题意
给出一张地图, “D” 表示门,”.” 表示人, “X” 表示墙。人不会出现在最外面一圈,门只会出现在最外面一圈,并且一扇门一秒钟只能有一人通过。现在起火了,求所有通过门逃出地图的最短时间。如果有人逃不出去就输出 impossible。
思路
看到这个题首先想到的是最小费用流。但是,“一秒钟只能通过一个人”这个限制条件无法克服。这个问题本质上是人和门的匹配问题,因为门多了一个时间属性,所以可以用拆点法归结成一个二分图匹配的问题。
1.把每个人看成一个点,构成集合 P;把每一时刻的门看成一个点,构成集合 D。因为最大的可能时间是地图的网格总数(设想为只有一扇门,其他格都站满了人的情况),所以集合 D 的规模在可接受的范围内。
2.从每一扇门出发进行bfs,求出每个人到这扇门的最短距离。
3.对于每一扇门,利用 2 中求出来的最短时间,把能到达它的人与对应时刻的它之间连一条边。
4.依次对 D 集合中的每一个点进行匹配,一旦匹配数达到 P, 则这个点对应的时间就是需要花费的最小时间。
5.如果时间达到了上界,但匹配数依然小于 P,输出 impossible 。
需要注意的一点是,这种做法需要特判 P = 0 的情况,否则在这种情况下会输出 impossible 。
题目链接
http://poj.org/problem?id=3057
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 13;
const int mx[4] = {0, 0, -1, 1};
const int my[4] = {1, -1, 0, 0};
int cas, row, col;
char Map[maxn][maxn];
int dis[maxn][maxn][maxn][maxn];//门到人的距离:这么存很暴力但是比较方便
vector<int> G[maxn * maxn * maxn * 5];
int match[maxn * maxn * maxn * 5];
bool usd[maxn * maxn * maxn * 5];
vector<int> dx, dy;//存门的坐标
vector<int> px, py;//存人的坐标
//bfs 求出门到各个人的最短距离
void bfs(int x, int y, int dis[maxn][maxn])
{
dis[x][y] = 0;
queue<int> qx, qy;
qx.push(x);qy.push(y);
while(qx.size())
{
x = qx.front();qx.pop();
y = qy.front();qy.pop();
for(int i= 0; i< 4; i++)
{
int cx = x + mx[i], cy = y + my[i];
if(cx >= 0 && cx < row && cy >= 0 && cy < col
&& Map[cx][cy] == '.' && dis[cx][cy] < 0)
{
dis[cx][cy] = dis[x][y] + 1;
qx.push(cx);
qy.push(cy);
}
}
}
}
void add(int a, int b)
{
G[a].push_back(b);
G[b].push_back(a);
}
bool dfs(int v)
{
usd[v] = true;
for(int i= 0; i< G[v].size(); i++)
{
int u = G[v][i], w = match[u];
if(w < 0 || !usd[w] && dfs(w))
{
match[u] = v;
match[v] = u;
return true;
}
}
return false;
}
int main()
{
cin >> cas;
while(cas --)
{
memset(match, -1, sizeof match);
memset(dis, -1, sizeof dis);
dx.clear(); dy.clear();
px.clear(); py.clear();
for(int i= 0; i< maxn * maxn * maxn * 5; i++)
G[i].clear();
cin >> row >> col;
int m = row * col;
for(int i= 0; i< row; i++)
for(int j= 0; j< col; j++)
cin >> Map[i][j];
for(int i= 0; i< row; i++)
for(int j= 0; j< col; j++)
{
if(Map[i][j] == 'D')
{
dx.push_back(i);
dy.push_back(j);
bfs(i, j, dis[i][j]);
}
else if(Map[i][j] == '.')
{
px.push_back(i);
py.push_back(j);
}
}
int D = dx.size(), P = px.size();
//重要特判
if(P == 0)
{
cout << "0\n";
continue;
}
for(int i= 0; i< D; i++)
for(int j= 0; j< P; j++)
{
//人 和 时刻门 之间连边
if(dis[dx[i]][dy[i]][px[j]][py[j]] > 0)
for(int t= dis[dx[i]][dy[i]][px[j]][py[j]]; t<= m; t++)
add((t - 1) * D + i, m * D + j);
}
int sum = 0;
//枚举匹配 时刻门
for(int i= 0; i< m * D; i++)
{
memset(usd, false, sizeof usd);
if(dfs(i))
{
sum ++;
if(sum == P)
{
cout << i / D + 1 << endl;
break;
}
}
}
if(sum < P)
cout << "impossible\n";
}
return 0;
}