给一个n*m的图, 里面有一些点, '.'代表空地, '#'代表墙, 不可以走, '@'代表大门, 可以有多个, 'X'代表人, 问所有人都走出大门需要的最短时间, 每一时刻一个格子只能有一个人, 每个时刻只能有一个人从大门走出, 如果不能走出, 输出-1。
先dfs判断是否每个人都能走出, 如果有人不能, 直接输出-1。
从小到大枚举时间, 对于枚举的时间t, 每个格子i, j向它四周以及他本身建边, ( i*m+j+(t-1)*nm, x*m+y+t*nm, 1), 这样就相当于t-1时刻的点向t时刻的点连了一条边。 然后每个出口向汇点连边, (x*m+y+t*nm, 汇点, 1)。 源点向0时刻的每个人连边, 只连一次。
这样每次都跑一遍网络流, 如果结果ans等于人数sum, 那么说明这个时刻是最小时刻, 需要注意的是, 如果结果不等于人数, 那么sum -= ans。
具体看代码。
#include<bits/stdc++.h>
using namespace std;
#define mem(a) memset(a, 0, sizeof(a))
#define mem1(a) memset(a, -1, sizeof(a))
#define mem2(a) memset(a, 0x3f, sizeof(a))
const int inf = ;
const int dir[][] = { {-, }, {, }, {, -}, {, },{, } };
const int maxn = 2e6+;
int q[maxn*], head[maxn*], dis[maxn/], s, t, m, n, vis[][], num, flag, sum;
char c[][];
struct node
{
int to, nextt, c;
node(){}
node(int to, int nextt, int c):to(to), nextt(nextt), c(c){}
}e[maxn*];
void init() {
num = flag = sum = ;
mem1(head);
}
void add(int u, int v, int c) {
e[num] = node(v, head[u], c); head[u] = num++;
e[num] = node(u, head[v], ); head[v] = num++;
}
int bfs() {
mem(dis);
dis[s] = ;
int st = , ed = ;
q[ed++] = s;
while(st<ed) {
int u = q[st++];
for(int i = head[u]; ~i; i = e[i].nextt) {
int v = e[i].to;
if(!dis[v]&&e[i].c) {
dis[v] = dis[u]+;
if(v == t)
return ;
q[ed++] = v;
}
}
}
return ;
}
int dfs(int u, int limit) {
if(u == t) {
return limit;
}
int cost = ;
for(int i = head[u]; ~i; i = e[i].nextt) {
int v = e[i].to;
if(e[i].c&&dis[v] == dis[u]+) {
int tmp = dfs(v, min(limit-cost, e[i].c));
if(tmp>) {
e[i].c -= tmp;
e[i^].c += tmp;
cost += tmp;
if(cost == limit)
break;
} else {
dis[v] = -;
}
}
}
return cost;
}
int dinic() {
int ans = ;
while(bfs()) {
ans += dfs(s, inf);
}
return ans;
}
// 上面是模板
int judge(int x, int y) {
if(x>=&&x<n&&y>=&&y<m&&c[x][y]!='#'&&!vis[x][y])
return ;
return ;
} int dfs1(int x, int y) {
for(int i = ; i<; i++) {
int tmpx = x+dir[i][];
int tmpy = y+dir[i][]; //判断能否走出
if(judge(tmpx, tmpy)) {
if(c[tmpx][tmpy]=='@')
return ;
vis[tmpx][tmpy] = ;
if(dfs1(tmpx, tmpy))
return ;
}
}
return ;
} int ok(int deep) { //枚举时间, 每一次枚举, 都在原有的图的基础上继续建边,因为原图已经跑过一遍最大流, 所以每次结束后
int nm = n*m; //总人数都应该减去每一次的结果, 意思是已经有那么多的人跑了出去。
for(int i = ; i<n; i++) {
for(int j = ; j<m; j++) {
if(c[i][j] == '@') {
add(i*m+j+nm*deep+, t, ); //对于每一时刻, 出口都要向汇点建边。
continue;
}
if(c[i][j]=='#')
continue;
for(int k = ; k<; k++) {
int x = i+dir[k][];
int y = j+dir[k][];
if(judge(x, y)) {
add(i*m+j+(deep-)*nm+, x*m+y+deep*nm+, ); //前一时刻的点向这一时刻建边。
}
}
}
}
int ans = dinic();
if(ans == sum)
return ;
sum -= ans; //这里十分重要啊....
return ;
} int main()
{
while(~scanf("%d%d", &n, &m)) {
init();
for(int i = ; i<n; i++)
scanf("%s", c[i]);
s = , t = ;
for(int i = ; i<n; i++) {
for(int j = ; j<m; j++) {
if(c[i][j] == 'X') {
add(s, i*m+j+, ); //源点向0时刻的每一个人连边
mem(vis);
sum++;
if(!dfs1(i, j)) {
flag = ;
}
}
}
}
if(flag) {
puts("-1");
continue;
}
mem(vis);
int ans;
for(ans = ; ; ans++) {
if(ok(ans)) //枚举时间
break;
}
cout<<ans<<endl;
}
}