题目链接:http://poj.org/problem?id=3057
题目翻译:
有一个X*Y的房间,每块区域只能是墙 ‘X’ , 空地'.' 或门 ’D‘。最外层
的区域一定是门或者墙壁,而内部区域一定没有门,假设房间发生火情,
每一个空地都站着一个人,而外侧的每个门每一秒只能出一个人,每
个空地每一秒也只能站一个人,一个人其逃跑路线只能是向上、向下、
向左、向右,问所有人都能顺利逃脱的最小时间,如果不能使所有人都
顺利逃脱输出impossible
题目思路:
来源于挑战程序设计竞赛。
其思路是搜索+二分图匹配。 相当难的一个题目,搜索还能想到,但是还
涉及到二分图匹配,真的想不到。
由于门每秒中只能出一个人,假如有多个人都能够在T秒内到达某个门,对于
这个门来说只能让一个人出,则其他几个人必然还要在等下一秒来看看有没有
可能逃出去。所以不能简单去广搜,已某个人距某个门的最大时间作为答案,
因为存在着等待过程。首先我们从每个门开始对人进行广搜,搜索任意一个人
到任意一个门的最短时间。
则我们得到如下想法:
某个人P到某个门D的最短时间是T,那么它可能在T秒或、T+1秒、或T+2.....从
这个门逃走。则二分图的集合分为两个,门组成的集合和人组成的集合。则人,门
合适该人从该门逃走,共同组成了一个状态。
因此我们对门采取下面编号:
0~d-1 :是第一秒内的门的编号。
d~2*d-1 :是第二秒内的门的编号。
2*d ~ 3*d-1:是第三秒内的门的编号。
..............
(注意虽然门是相同的门,但是其时间不同,我们视其状态不同)
那么时间的上限应该以多久为上限呢?最大的时间我们设为矩阵面积,因为在
这个时间内如果某个人不是被墙围死了,或者区域根本就没有门,那么在这个
时间内所有人肯定都逃除去完了。
因此门的编号就到 (X*Y-1)*d ~ (X*Y)*d - 1:
而人的编号顺着这个编号编下去即可。
某个人P到某个门D的最短时间是T,那么它可能在T秒或、T+1秒、或T+2.....从该门
逃走,则P应该在T秒与D建立关系,T+1秒与D建立关系,T+2秒与D建立关系,
这些关系对应二分图中的一条边。
然后进行二分图匹配,匹配数目等于人数的时候算法结束。
AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 50;
int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};
char Map[maxn][maxn];
int X,Y;
int dist[maxn][maxn][maxn][maxn];
int match[10000],mark[10000];
vector<int>DX,DY;
vector<int>PX,PY;
vector<int>g[10000];
queue<int>qu1;
queue<int>qu2;
void bfs(int x,int y,int d[maxn][maxn])
{
qu1.push(x); ///起点横坐标进队
qu2.push(y); ///起点纵坐标进队
d[x][y] = 0; ///开始步数为0
int x1,y1,x2,y2;
while(!qu1.empty())
{
x1 = qu1.front();
qu1.pop();
y1 = qu2.front();
qu2.pop();
///4个方向进行广搜
for(int i = 0; i < 4; i++)
{
x2 = x1 + dir[i][0];
y2 = y1 + dir[i][1];
if(x2>=0&&x2<X&&y2>=0&&y2<Y&&Map[x2][y2]=='.'&&d[x2][y2]<0)
{
d[x2][y2] = d[x1][y1] + 1;
qu1.push(x2);
qu2.push(y2);
}
}
}
}
bool hungry(int u)
{
mark[u] = 1;
for(int i = 0; i < (int)g[u].size(); i++)
{
int v = g[u][i];
int w = match[v];
if(w < 0 || (mark[w]==0 && hungry(w)))
{
match[u] = v;
match[v] = u;
return true;
}
}
return false;
}
void allSurvive()
{
int d = DX.size(); ///d是门的数量
int p = PX.size(); ///p是人的数量
/**最大的时限是矩阵的面积*/
int n = X*Y;
/**0~d-1 :时间1对应的门
d~2*d-1 : 时间2对应的门
2*d~3*d-1 : 时间3内对应的门
.......
(n-1)*d ~ n*d-1:时间n内对应的门
n*d ~ n*d+p-1 : 对应人的编号
*/
int V = n*d + p;
for(int i = 0; i < V; i++)
g[i].clear();
for(int i = 0; i < d; i++)
{
for(int j = 0; j < p; j++)
{
if(dist[DX[i]][DY[i]][PX[j]][PY[j]] >= 0)
{
for(int k = dist[DX[i]][DY[i]][PX[j]][PY[j]]; k <= n; k++)
{
int u = (k-1)*d + i;
int v = n*d + j;
g[u].push_back(v);
g[v].push_back(u);
}
}
}
}
memset(match,-1,sizeof(match));
int ccount = 0;
for(int i = 0; i < n*d; i++)
{
memset(mark,0,sizeof(mark));
if(hungry(i))
{
ccount++;
if(ccount == (int)PX.size())
{
printf("%d\n",i/d+1);
return;
}
}
}
printf("impossible\n");
return;
}
int main()
{
int T;
scanf("%d",&T); ///T组测试数据
while(T--)
{
DX.clear(); ///存放门的横坐标,首先清空
DY.clear(); ///存放门的纵坐标,首先清空
PX.clear(); ///存放人的横坐标,首先清空
PY.clear(); ///存放人的纵坐标,首先清空
scanf("%d%d",&X,&Y); ///输入矩阵规模
for(int i = 0; i < X; i++)
{
for(int j = 0; j < Y; j++)
{
scanf(" %c",&Map[i][j]); ///注意吞掉空格
if(Map[i][j] == 'D')
{
DX.push_back(i);
DY.push_back(j);
}
else if(Map[i][j] == '.')
{
PX.push_back(i);
PY.push_back(j);
}
}
}
if((int)PX.size() == 0) ///人的人数为0.
printf("0\n");
else
{
memset(dist,-1,sizeof(dist));
for(int i = 0; i < (int)DX.size(); i++)
{
bfs(DX[i],DY[i],dist[DX[i]][DY[i]]); ///以门为起点进行广搜
}
allSurvive(); ///调用函数求所有人都逃生成功的时间
}
}
return 0;
}