标签:
题目链接:点击打开链接
Description
现在有一个由N行M列个单元格组成的游戏地图,从上至下依次记为第1行,第2行,……,第N行,从左至右依次记为第1列,第2列,……,第M列。不妨用(x, y)来表示第x行第y列的那个单元格。
目前你位于(1, 1),而你想前往(N, M)并跳转到下一个地图中,但是这幅地图上却遍布着一些相同地雷,地雷的感知半径为K,如果你踏入的单元格距离某颗地雷的曼哈顿距离不超过K,那么这颗地雷就会爆炸并炸伤你,游戏也就结束了。不过幸运的是你可以用一枚金币的代价清除这个地图上任意一颗地雷,那么你最少需要使用多少枚金币来清除地雷才能保证顺利从(1, 1)走到(N, M)呢?你在任何一个单元格时,都可以向上、向下、向左或者向右走到相邻的单元格,但是不能走到地图之外。
Output
对于每组测试数据,用一行输出一个整数,表示至少需要使用多少枚金币来清除地雷。
思路:
我们把所有能互相影响的地雷连线(所谓互相影响就是两个地雷的爆炸范围会覆盖或者把路堵死)
那么连线以后只有以下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;
}
CSU 1355 地雷清除计划 网格图清除最少的地雷使得对角联通 最小割
标签:
原文地址:http://blog.csdn.net/qq574857122/article/details/43087363