题解:B4400 [蓝桥杯青少年组国赛 2025] 第五题

题解:B4400 [蓝桥杯青少年组国赛 2025] 第五题

Leo2011 警示后人

闲话

板题,有一定码量和小细节(但是写起来很快,对称的复制粘贴即可),在这种只有 分钟的比赛中出这种题的组委会有点难评。

因此虽然思路简单,放个 T5 还是合适的。

题意

简要题意:有三个牛奶瓶 A、B、C,容积分别为 v,x,y 升,其中最开始 A 灌满了 v 升,B、C 没有牛奶。

每次操作可以任选两个瓶子倒牛奶:

  • 如果可以倒满那就倒满。
  • 否则全部倒入。

求是否可能让其中一个瓶子恰好 升牛奶,如果可以输出最小操作次数,否则输出 -1

做法

首先过掉一部分无解的情况,如果 是奇数那整数内部加减当然不可能有分数,因此是无解的。

鉴于数据范围很小,那当然是考虑搜索、枚举啦。稍微有些经验应该能看出来这是道经典的 BFS 入门练习题。

BFS 的做法是,我们维护一个状态队列,每次不断从队首得到新的状态然后将队首出队。如果队首已经有满足情况的就直接结束了,否则继续搜索。初始状态当然是 中有 升, 没有,操作次数是 ,信息略多可以用一个结构体维护。

接着,我们模拟题目中的倒牛奶过程,即 A 倒入 B、A 倒入 C、B 倒入 C,然后每种操作再分两类讨论。由于大差不差的,直接复制粘贴 3 组 条判断语句即可。操作完得到新的状态之后,将操作次数 +1 得到新的操作次数然后重新丢进队列里就算结束了。

但是这样子会超时的!考虑如下情况:

1
6 2 1

第一次,我们将 A 倒入 B,此时 A 还剩 升, 升。

第二次,我们将 B 倒入 A,此时 A 有 升,B 空了。

哎这怎么又回去了?

我们发现我们需要存下来已经搜索过的状态,开一个三维数组记录即可(这也是为什么这题数据范围这么小,大一些开三维就 MLE 了)。

如果之前搜索过了就不用继续搜了(因为只会搜以前搜过的状态,反而步数更多了不优,某种意义上这也算是一个简单的最优性剪枝)。

最后,注意如果状态队列空了但是还没有结果那也是无解的,仍然返回 -1

然后就结束啦!


DFS 也差不多,也可以做,也需要维护一个状态数组,只不过从队列改成递归搜索就好啦!

代码

赛场 ACCode

图片版:Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <bits/stdc++.h>

using namespace std;

const int N = 500;
int v, x, y, vis[N][N][N], goal;

struct st {
int a, b, c, stp;
};

queue<st> q;

int bfs() {
q.push({v, 0, 0, 0});
while (!q.empty()) {
st f = q.front();
q.pop();
if (f.a == goal || f.b == goal || f.c == goal) return f.stp;
if (vis[f.a][f.b][f.c]) continue;
else vis[f.a][f.b][f.c] = 1;
if (f.a > x - f.b) q.push({f.a - (x - f.b), x, f.c, f.stp + 1});
else q.push({0, f.b + f.a, f.c, f.stp + 1});
if (f.a > y - f.c) q.push({f.a - (y - f.c), f.b, y, f.stp + 1});
else q.push({0, f.b, f.c + f.a, f.stp + 1});

if (f.b > v - f.a) q.push({v, f.b - (v - f.a), f.c, f.stp + 1});
else q.push({f.b + f.a, 0, f.c, f.stp + 1});
if (f.b > y - f.c) q.push({f.a, f.b - (y - f.c), y, f.stp + 1});
else q.push({f.a, 0, f.c + f.b, f.stp + 1});

if (f.c > v - f.a) q.push({v, f.b, f.c - (v - f.a), f.stp + 1});
else q.push({f.c + f.a, f.b, 0, f.stp + 1});
if (f.c > x - f.b) q.push({f.a, x, f.c - (x - f.b), f.stp + 1});
else q.push({f.a, f.b + f.c, 0, f.stp + 1});
}
return -1;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0); // 常数优化
cin >> v >> x >> y;
if (v % 2) { // 偶数 % 2 是 0,判断为 false,不会进入判断
cout << -1;
return 0;
}
goal = v >> 1; // 对于正整数,右移一位相当于除以 2
cout << bfs() << endl;
return 0;
}

如有除注释以外的差异以图片为准,文字版可以通过。

AC 记录:https://www.luogu.com.cn/record/233679115


DFS 的也有:

ACCode with 注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>

using namespace std;

const int N = 201;
int v, x, y, vis[N][N][N], ret = INT_MAX, goal; // ret 最开始要是一个极大值,否则怎么找都是 0

struct st {
int a, b, c, stp;
};

void dfs(int a, int b, int c, int stp) {
if (a == goal || b == goal || c == goal) {
ret = min(ret, stp); // 取最小值,dfs 不能保证第一次搜到的是最小的因此要记录一下,而 bfs 可以因此搜到了就可以直接返回结束了
return; // 不 return 就会继续搜下去然后就无限递归啦!
}
if (stp > vis[a][b][c] && vis[a][b][c]) return; // dfs 不能保证第一次搜到的就是最小的,因此只有步数大于已经搜过的步数时候才是更劣的
else vis[a][b][c] = stp; // 仍然记录步数
if (a > x - b) dfs(a - (x - b), x, c, stp + 1); // A 倒 B
else dfs(0, b + a, c, stp + 1);
if (a > y - c) dfs(a - (y - c), b, y, stp + 1); // A 倒 C
else dfs(0, b, c + a, stp + 1);

if (b > v - a) dfs(v, b - (v - a), c, stp + 1); // B 倒 A
else dfs(b + a, 0, c, stp + 1);
if (b > y - c) dfs(a, b - (y - c), y, stp + 1); // B 倒 C
else dfs(a, 0, c + b, stp + 1);

if (c > v - a) dfs(v, b, c - (v - a), stp + 1); // C 倒 A
else dfs(c + a, b, 0, stp + 1);
if (c > x - b) dfs(a, x, c - (x - b), stp + 1); // C 倒 B
else dfs(a, b + c, 0, stp + 1);
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> v >> x >> y;
if (v % 2) {
cout << -1;
return 0;
}
goal = v >> 1;
dfs(v, 0, 0, 0);
cout << (ret == INT_MAX ? -1 : ret) << endl; // 不行就是 -1,注意判断 ret
return 0;
}

AC 记录:https://www.luogu.com.cn/record/233894967

提示:memset 会对数组中的每一个元素赋值,三维数组这么大当然时空也会超级大(见这篇帖子),因此不建议使用 memset 初始化而是判断步数是否为 0。此处感谢 @ETO_NOI 的提醒。

完结撒花!

提示:这里情况比较多,空行区分和注释可以大幅减少 debug 的时间和代码阅读难度,相比于花大量的时间调不可读的代码甚至比赛结束也做不完,敲它们的时间是非常划算的。

推荐练习

P1135 奇怪的电梯。和这道题差不多,但是状态要少很多(因为只有向上和向下两种情况)因此要简单一丢丢。

  • 标题: 题解:B4400 [蓝桥杯青少年组国赛 2025] 第五题
  • 作者: Leo2011
  • 创建于 : 2025-08-29 11:06:53
  • 更新于 : 2025-11-18 16:00:59
  • 链接: https://www.leo2011.eu.org/2025/0829caa27ec3.html
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
Nickname
Email
Website
  • OωO
  • |´・ω・) ノ
  • ヾ (≧∇≦*) ゝ
  • (☆ω☆)
  • (╯‵□′)╯︵┴─┴
  •  ̄﹃ ̄
  • (/ω\)
  • ∠( ᐛ 」∠)_
  • (๑•̀ㅁ•́ฅ)
  • →_→
  • ୧(๑•̀⌄•́๑)૭
  • ٩(ˊᗜˋ*)و
  • (ノ °ο°) ノ
  • (´இ皿இ`)
  • ⌇●﹏●⌇
  • (ฅ´ω`ฅ)
  • (╯°A°)╯︵○○○
  • φ( ̄∇ ̄o)
  • ヾ (´・ ・`。) ノ "
  • (ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
  • (ó﹏ò。)
  • Σ(っ °Д °;) っ
  • (,,´・ω・)ノ"(´ っ ω・`。)
  • ╮(╯▽╰)╭
  • o(*////▽////*)q
  • >﹏<
  • ( ๑´•ω•) "(ㆆᴗㆆ)
  • 😂
  • 😀
  • 😅
  • 😊
  • 🙂
  • 🙃
  • 😌
  • 😍
  • 😘
  • 😜
  • 😝
  • 😏
  • 😒
  • 🙄
  • 😳
  • 😡
  • 😔
  • 😫
  • 😱
  • 😭
  • 💩
  • 👻
  • 🙌
  • 🖕
  • 👍
  • 👫
  • 👬
  • 👭
  • 🌚
  • 🌝
  • 🙈
  • 💊
  • 😶
  • 🙏
  • 🍦
  • 🍉
  • 😣
  • 颜文字
  • Emoji
  • Bilibili
0 comments
No comment
目录
题解:B4400 [蓝桥杯青少年组国赛 2025] 第五题