题目链接:点击打开链接
Description
现在有一个由N行M列个单元格组成的游戏地图,从上至下依次记为第1行,第2行,……,第N行,从左至右依次记为第1列,第2列,……,第M列。不妨用(x, y)来表示第x行第y列的那个单元格。
目前你位于(1, 1),而你想前往(N, M)并跳转到下一个地图中,但是这幅地图上却遍布着一些相同地雷,地雷的感知半径为K,如果你踏入的单元格距离某颗地雷的曼哈顿距离不超过K,那么这颗地雷就会爆炸并炸伤你,游戏也就结束了。不过幸运的是你可以用一枚金币的代价清除这个地图上任意一颗地雷,那么你最少需要使用多少枚金币来清除地雷才能保证顺利从(1, 1)走到(N, M)呢?你在任何一个单元格时,都可以向上、向下、向左或者向右走到相邻的单元格,但是不能走到地图之外。
Input
输入的第一行包含一个整数T (1 <= T <= 100),表示一共有T组测试数据。
对于每组测试数据,第一行包含三个整数N, M, K ( 1 <= N, M<= 50, 0<= K < 30),含义同上。接下来一共有N行,每行均有M个字符,描述了这个游戏地图。其中’.’表示没有地雷的单元格,’*’表示有地雷的单元格(如果该地雷被清除了,那么这个格子就会变成没有地雷的单元格)。数据保证地雷的总数量不超过200,(1, 1)和(N, M)距离任何一颗地雷的曼哈顿距离都大于K。
Output
对于每组测试数据,用一行输出一个整数,表示至少需要使用多少枚金币来清除地雷。
Sample Input
3 5 5 1 ..*.. ....* ..... *.... ..*.. 4 6 1 ..*... ...... ...... ...*.. 3 3 0 .** *** **.
Sample Output
0 1 3
思路:
我们把所有能互相影响的地雷连线(所谓互相影响就是两个地雷的爆炸范围会覆盖或者把路堵死)
那么连线以后只有以下4种线是会拦路的:
1、左边界和上边界相连的线
2、左边界和有边界相连的线
3、下边界和上边界相连的线
4、下边界和有边界相连的线
其实我们要做的事情就是尽可能少地剪断这样线,使得剪断后不存在这样的线能使左下边界和右上边界相连
把左下边界和右上边界简化成一个点,就是求个最小割。
类似于这题点击打开链接
#include <cstdio> #include <algorithm> #include <string.h> #include <queue> #include <cstring> #include <cmath> #include <iostream> #include <vector> using namespace std; #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std; //点标 [0,n] const int N = 1005; const int M = 500010; const int INF = ~0u >> 2; template<class T> struct Max_Flow { int n; int Q[N], sign; int head[N], level[N], cur[N], pre[N]; int nxt[M], pnt[M], E; T cap[M]; void Init(int n) { this->n = n + 1; E = 0; std::fill(head, head + this->n, -1); } //有向rw 就= 0 void add(int from, int to, T c, T rw = 0) { pnt[E] = to; cap[E] = c; nxt[E] = head[from]; head[from] = E++; pnt[E] = from; cap[E] = rw; nxt[E] = head[to]; head[to] = E++; } bool Bfs(int s, int t) { sign = t; std::fill(level, level + n, -1); int *front = Q, *tail = Q; *tail++ = t; level[t] = 0; while (front < tail && level[s] == -1) { int u = *front++; for (int e = head[u]; e != -1; e = nxt[e]) { if (cap[e ^ 1] > 0 && level[pnt[e]] < 0) { level[pnt[e]] = level[u] + 1; *tail++ = pnt[e]; } } } return level[s] != -1; } void Push(int t, T &flow) { T mi = INF; int p = pre[t]; for (int p = pre[t]; p != -1; p = pre[pnt[p ^ 1]]) { mi = std::min(mi, cap[p]); } for (int p = pre[t]; p != -1; p = pre[pnt[p ^ 1]]) { cap[p] -= mi; if (!cap[p]) { sign = pnt[p ^ 1]; } cap[p ^ 1] += mi; } flow += mi; } void Dfs(int u, int t, T &flow) { if (u == t) { Push(t, flow); return; } for (int &e = cur[u]; e != -1; e = nxt[e]) { if (cap[e] > 0 && level[u] - 1 == level[pnt[e]]) { pre[pnt[e]] = e; Dfs(pnt[e], t, flow); if (level[sign] > level[u]) { return; } sign = t; } } } T Dinic(int s, int t) { pre[s] = -1; T flow = 0; while (Bfs(s, t)) { std::copy(head, head + n, cur); Dfs(s, t, flow); } return flow; } }; Max_Flow <int>F; typedef pair<int, int> pii; #define pb push_back const int MAXN = 55; int n, m, r, from, to; char mp[MAXN][MAXN]; vector<pii>G; int dis(pii x, pii y){ return abs(x.first - y.first) + abs(x.second - y.second); } void build(){ from = G.size() * 2 + 1, to = from + 1; F.Init(to); for (int i = 0; i < G.size(); i++){ int x = G[i].first, y = G[i].second; F.add(i * 2, i * 2 + 1, 1); if (y - r <= 1 || x + r >= n)//碰到左边界或下边界 F.add(from, i * 2, INF); if (y + r >= m || x - r <= 1)//碰到右边界或上边界 F.add(i * 2 + 1, to, INF); for (int j = i+1; j < G.size(); j++) { bool touch = false; if ((x == G[j].first || y == G[j].second) && dis(G[i], G[j]) <= 2 * r + 1)touch = true; if (x != G[j].first && y != G[j].second && dis(G[i], G[j]) <= 2 * r + 2)touch = true; if (touch){ F.add(i * 2 + 1, j * 2, INF); F.add(j * 2 + 1, i * 2, INF); } } } } void input(){ G.clear(); scanf("%d %d %d", &n, &m, &r); for (int i = 1; i <= n; i++) { scanf("%s", mp[i] + 1); for (int j = 1; j <= m; j++) if (mp[i][j] == '*') G.pb(pii(i, j)); } } int main(){ int T; scanf("%d", &T); while (T--){ input(); if (G.size() == 0){ puts("0"); continue; } build(); printf("%d\n", F.Dinic(from, to)); } return 0; }