题目描述
难度分:1673
输入n,m(1≤n,m≤300),t(1≤t≤2×106)和一个n行m列的网格图。
-
S
为起点(恰好一个)。 -
G
为终点(恰好一个)。 -
.
为空地。 -
#
为墙壁。 -
o
为糖果(至多18个)。
你需要从起点走到终点。每一步可以上下左右四个方向走到相邻的非墙壁格子上,不能出界,可以重复访问同一个格子。
注:如果走到终点,可以继续走,只要最后在终点就行,至多走t步。
输出你至多能收集多少个糖果(走到o
即可收集,同一个格子只能收集一次)。
如果无法在t步内到达终点,输出−1。
输入样例1
3 3 5
S.G
o#o
.#.
输出样例1
1
输入样例2
3 3 1
S.G
.#o
o#.
输出样例2
-1
输入样例3
5 10 2000000
S.o..ooo..
..o..o.o..
..o..ooo..
..o..o.o..
..o..ooo.G
输出样例3
18
算法
BFS
+状压DP
这个题乍一看感觉是暴力深搜或者宽搜,但仔细想想这么做肯定会超时。主要的突破点在于糖果的数量最多为18个,这个数量是可以状压的,算上起点和终点,220大概是106的量级,很有可能是一个状压DP
。
状态定义
为了方便处理,把起点和终点都看做糖果,f[mask][i]表示当前在i位置,且收集糖果的状态为mask的情况下,最少的步数。
状态转移
对于状态f[mask][j],枚举它的下一个位置i就可以进行状态转移,转移方程为
f[mask∨2i][i]=minj(f[mask][j]+dist[j][i])
其中j可以到达i,dist[j][i]表示j到i的最短路,可以枚举糖果对,通过BFS
计算糖果两两之间的最短距离给预处理出来。
最后枚举mask,状态要包含起点和终点,维护f[mask][dest]中1的最大值mx即可(其中dest为终点),答案还需要去掉起点和终点,因此为mx−2。
复杂度分析
设糖果的数量为m,地图的高和宽是n的规模。
时间复杂度
利用BFS求起点、终点、以及每个糖果之间最短距离时间复杂度为O(m2n2)。之后的状压DP
,状态数量为2m规模,算上内层两层循环枚举两个相邻的位置,时间复杂度为O(m22m)。因此,算法整体的时间复杂度为O(n2m2+m22m)。
空间复杂度
dp数组是O(m2m)规模,dist数组是O(m2)规模,其他的辅助数组都是O(n2)规模。考虑到n比m大很多,因此额外空间的痛点在于O(n2),这也是整个算法的额外空间复杂度。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 301, M = 21, INF = 0x3f3f3f3f;
const int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int h, w, t, n, so, to;
char g[N][N];
int dist[M][M], f[1<<M][M];
bool st[N][N];
int bfs(int x1, int y1, int x2, int y2) {
memset(st, 0, sizeof st);
int step = 0;
queue<PII> q;
q.push({x1, y1});
while(!q.empty()) {
int sz = q.size();
while(sz--) {
auto cur = q.front();
q.pop();
int x = cur.first, y = cur.second;
if(x == x2 && y == y2) return step;
if(st[x][y]) continue;
st[x][y] = true;
for(int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if(1 <= nx && nx <= h && 1 <= ny && ny <= w && g[nx][ny] != '#') {
q.push({nx, ny});
}
}
}
step++;
}
return INF;
}
int main() {
scanf("%d%d%d", &h, &w, &t);
vector<PII> p;
for(int i = 1; i <= h; i++) {
scanf("%s", g[i] + 1);
for(int j = 1; j <= w; j++) {
if(g[i][j] == 'S') {
so = p.size();
g[i][j] = 'o';
p.push_back({i, j});
}else if(g[i][j] == 'G') {
to = p.size();
g[i][j] = 'o';
p.push_back({i, j});
}else if(g[i][j] == 'o') {
p.push_back({i, j});
}
}
}
n = p.size();
swap(p[to], p[n - 1]); to = n - 1;
swap(p[so], p[n - 2]); so = n - 2;
memset(dist, 0x3f, sizeof dist);
for(int i = 0; i < n; i++) {
for(int j = i; j < n; j++) {
if(i == j) {
dist[i][j] = 0;
}else {
dist[i][j] = dist[j][i] = bfs(p[i].first, p[i].second, p[j].first, p[j].second);
}
}
}
if(dist[so][to] > t) {
puts("-1"); // 起点终点不连通
return 0;
}
if(n <= 2) {
puts("0"); // 图上出起点终点之外没有糖果
return 0;
}
// 状压DP
memset(f, 0x3f, sizeof(f));
f[1<<(n - 2)][n - 2] = 0; // 刚开始在起点
for(int mask = 0; mask < 1<<n; mask++) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
f[mask|(1<<i)][i] = min(f[mask|(1<<i)][i], f[mask][j] + dist[j][i]);
}
}
}
int ans = 0;
for(int mask = 0; mask < 1<<n; mask++) {
if((mask>>(n - 1)&1) == 0 || (mask>>(n - 2)&1) == 0) continue; // 必须包含起点和终点
if(f[mask][n - 1] <= t) {
ans = max(ans, __builtin_popcount(mask));
}
}
printf("%d", ans - 2);
return 0;
}