[luogu P3786] 萃香抱西瓜 题解
题目链接:P3786 萃香抱西瓜
题面
题目描述
萃香在一个 \(n\times m\) 的空间中,这个空间中 \(s\) 个时刻内会陆续出现一些西瓜 (Suika) 。
萃香力气有限,她可以拿走一部分小西瓜,但不能拿走其余的大西瓜,西瓜在一段时间内会出现在空间中,西瓜每一个时刻的出现位置都是随机的,不一定连续,也有可能不动,我们认为西瓜的移动在瞬间完成,即第 \(i\) 个时刻若西瓜出现在 \((x,y)\) ,则我们认为萃香只会在 \((x,y)\) 这个坐标遇到它。
萃香希望拿走出现的所有小西瓜,并且不被大西瓜砸中。翠香最开始时站在 \((sx, sy)\) 处,每个时刻她可以向上下左右任何一个方向走一步,或者在原地等待一个时刻,问最后至少需要移动几步。
如果萃香无法拿到所有小西瓜,算做失败,另外,即使最后萃香已经拿到了所有的小西瓜,但若在 \(s\) 个时刻结束前萃香被大西瓜砸到仍然算作失败,如果成功,输出最少移动步数,否则输出 -1
。
输入格式
第一行五个整数 \(n,m,s,sx,sy\) ,含义见题目描述。
第二行两个整数 \(c_1,c_2\) ,表示一共有 \(c_1\) 个西瓜,其中 \(c_2\) 个是小西瓜。
接下来 \(c_1\) 段数据,每段数据描述了一个西瓜:
第一行两个整数 \(t_1,t_2\) ,表示这个西瓜的出现世界和消失时间,特别的,若 \(t_2=s+1\) ,则表示这个西瓜不会消失。
第二行一个整数 \(op\) 为 \(0/1\) ,如果是 \(0\) 则表示这个西瓜需要躲避,否则表示这个西瓜需要拿走。
接下来 \(t_2-t_1\) 行,第 \(i\) 行两个整数 \(x,y\) 表示在第 \(t_1+i-1\) 这个时刻这个西瓜出现在坐标 \((x,y)\) 处。
输出格式
一行一个整数表示萃香的最少移动步数或 -1
。
数据规模
\(1\le n,m \le 10\)
\(1\le s\le 10\)
\(1\le t1<t2\le s+1\)
\(1\le sx,x\le n\)
\(1\le sy,y\le m\)
\(1\le c_1\le 20\)
\(1\le c_2\le \min\{c_1,10\}\)
数据保证 \(op=1\) 的数据段只会出现 \(c_2\) 次。
数据保证同一时刻不会有两个西瓜出现在同一个坐标上。
题解
算法:状压 + 三维 bfs
这道题目虽然顶着紫题的名头,但是难度却只有黄题或绿题左右,思维和码量都始终,可以作为普转提的题目来刷。
通过观察数据规模,我们可以发现整个数据规模很小,地图和时间都只有 \(100\) ,要拿的西瓜只有 \(10\) 个,所以我们可以思考二进制状压存储某个西瓜有没有被拿过。
观察题面,把时间转化成一维存起来,发现这道题是一道三维最短路,其中边权只有 \(0\)(原地等待)和 \(1\)(向四个方向移动),所以可以很自然地想到双端队列 bfs 解决,接下来这道题就解决了。
复杂度分析
bfs 的复杂度取决于状态数,本题状态开了四维,分别是一维时间,两维地图,再加一维状压表示西瓜有没有拿过,但因为大西瓜阻挡等因素,不一定跑满,所以时间复杂度就是 \(\text{O}(nms\cdot2^{c_2})\) ,空间复杂度是 \(\Theta(nms\cdot2^{c_2})\)
c++ 代码
#include<bits/stdc++.h>
using namespace std;
#define Reimu inline void // 灵梦赛高
#define Marisa inline int // 魔理沙赛高
#define Sanae inline bool // 早苗赛高
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> Pii;
typedef tuple<int, int, int> Tiii;
#define fi first
#define se second
const Pii D[] = {{0, 0}, {0, 1}, {0, -1}, {1, 0}, {-1, 0}};
inline Pii operator+(Pii a, Pii b) { return {a.fi + b.fi, a.se + b.se}; }
const int N = 10, M = 110;
struct Node { Pii loc; int ti, sta; };
int n, m, c, c1, c2, tot, frX, frY;
int mp[N][N][M], d[N][N][M][1 << N];
Marisa bfs() {
if (!~mp[frX][frY][1]) return -1;
memset(d, -1, sizeof d);
deque<Node> Q;
d[frX][frY][1][mp[frX][frY][1]] = 0; Q.push_back({{frX, frY}, 1, mp[frX][frY][1]});
while (!Q.empty()) {
auto[x, ti, sta] = Q.front(); Q.pop_front();
if (ti == c) {
if (sta == (1 << c2) - 1) return d[x.fi][x.se][ti][sta];
continue;
}
for (auto delta: D) {
Pii y = x + delta;
int cost = delta.fi || delta.se;
int Ti = ti + 1, Sta = sta | mp[y.fi][y.se][Ti];
if (!y.fi || y.fi > n || !y.se || y.se > n || !~mp[y.fi][y.se][Ti] || ~d[y.fi][y.se][Ti][Sta]) continue;
d[y.fi][y.se][Ti][Sta] = d[x.fi][x.se][ti][sta] + cost;
if (cost) Q.push_back({y, Ti, Sta});
else Q.push_front({y, Ti, Sta});
}
}
return -1;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m >> c >> frX >> frY >> c1 >> c2;
while (c1--) {
int t1, t2, ty; cin >> t1 >> t2 >> ty;
for (int i = t1, tag = ty ? 1 << tot++ : -1, x, y; i < t2; ++i) cin >> x >> y, mp[x][y][i] |= tag;
}
cout << bfs();
return 0;
}