Evacuation 二分图匹配 + 最短路

Fires can be disastrous, especially when a fire breaks out in a room that is completely filled with people. Rooms usually have a couple of exits and emergency exits, but with everyone rushing out at the same time, it may take a while for everyone to escape.

You are given the floorplan of a room and must find out how much time it will take for everyone to get out. Rooms consist of obstacles and walls, which are represented on the map by an 'X', empty squares, represented by a '.' and exit doors, which are represented by a 'D'. The boundary of the room consists only of doors and walls, and there are no doors inside the room. The interior of the room contains at least one empty square.

Initially, there is one person on every empty square in the room and these persons should move to a door to exit. They can move one square per second to the North, South, East or West. While evacuating, multiple persons can be on a single square. The doors are narrow, however, and only one person can leave through a door per second.

What is the minimal time necessary to evacuate everybody? A person is evacuated at the moment he or she enters a door square.

Input

The first line of the input contains a single number: the number of test cases to follow. Each test case has the following format:
One line with two integers Y and X, separated by a single space, satisfying 3 <= Y, X <= 12: the size of the room
Y lines with X characters, each character being either 'X', '.', or 'D': a valid description of a room

Output

For every test case in the input, the output should contain a single line with the minimal evacuation time in seconds, if evacuation is possible, or "impossible", if it is not.

Sample Input

3
5 5
XXDXX
X...X
D...X
X...D
XXXXX
5 12
XXXXXXXXXXXX
X..........D
X.XXXXXXXXXX
X..........X
XXXXXXXXXXXX
5 5
XDXXX
X.X.D
XX.XX
D.X.X
XXXDX

Sample Output

3
21
impossible

题目大意:有一个Y*X的着了火的房间,里面有一些人和一些门。一个门在单位时间内只能让一个人通过,问让所有人逃生最少需要多少时间?

解法:首先想到求每个人到每个门的最短路再取最大,但是因为题目中的限制,每个门单位时间内只能允许一个人通过,因此简单的最短路求解无法得出正确答案。然后 我们想到可以用 二分图匹配 算法, 将每个门在时间t内能允许通过的人的集合计算出来,然后与所有的人相匹配,最后得出的匹配数如果>=n的话就说明时间t内可以让所有人逃走。

那么时间t的枚举我们使用二分吗?通过分析可以发现,时间t增加的时候仅仅是在二分图的一边增加点,用不着 每次都重新算一遍。换句话说 只要每次增加新的点和边,然后找增广路就可以了。找到增广路就将匹配数+1,当匹配数到达总人数就说明可以在时间t内全部逃出了。

我们在建立二分图的时候,用 X*Y*d 个点来表示时间和门的所有组合,再用其他p个点来表示人。先计算出所有人到所有门的最短距离,然后对于人 i 和门 j ,假如他们的最短距离为x,那么从 x 到  X*Y 的时间范围内人 i 都能通过门 j 逃生。然后我们就可以把 时间 k 和 门 j 的组合 与 人 i 连一条边了。(k在x到X*Y的范围内)

在跑最短路的时候,因为是多个起点,所以每个门都要跑一边spfa。(好在这题数据小)

这一题还有一点需要注意的就是用一个数字表示一种状态,比如用x*d+i表示第i个门,第x秒钟的 二元组。然后用vector <int> G[maxv] 来记录时间x以内能从i门逃出的人的集合(也就是连边)。

代码:

#include <cstdio>
#include <vector>
#include <queue>
#include <string.h>
using namespace std;
const int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};

vector <int> G[50010]; 
int X,Y,n,match[50010];
int dist[20][20][20][20];         //dist数组记录每个人到每个门的最短距离 
bool used[50010];
char field[20][20];
vector <int> dX,dY;
vector <int> pX,pY;

void bfs(int x,int y,int d[20][20])      //spfa跑最短路,此处以这个门为起点 
{
	queue<int> qx,qy;
	d[x][y]=0;             //初始化门到自己的距离为零 
	qx.push(x);
	qy.push(y);
	while (!qx.empty()){
		x=qx.front(); qx.pop();
		y=qy.front(); qy.pop();
		for (int k=0; k<4; k++)        //搜索 
		{
			int x2=x+dx[k],y2=y+dy[k];
			if ( x2>=0 && x2<X && 0<=y2 && y2<Y && field[x2][y2]=='.' && d[x2][y2]<0)
			//更新 入队 
			{
				d[x2][y2]=d[x][y]+1;    
				qx.push(x2);
				qy.push(y2);
			}
		}
	}
}

void addedge(int u,int v){
	G[u].push_back(v);
	G[v].push_back(u);
}

bool dfs(int v)
{
	used[v]=true;
	
	for (int i=0; i<G[v].size(); i++)
	{
		int u=G[v][i];
			
		if ( match[u]==-1 || !used[match[u]] && dfs(match[u]) )
		{
			match[v]=u;
			match[u]=v;
			return true;		
		}
	}
	
	return false;
}


int main()
{
	int t;
	scanf("%d", &t);
	while (t--)               //多组数据 
	{
		scanf("%d%d", &X, &Y);
		
		dX.clear(); dY.clear();        //清空vector 
		pX.clear(); pY.clear(); 
		memset(dist, -1, sizeof(dist));    //初始化距离数组 
		
		for (int i=0; i<X; i++)
		{
			scanf("%s", &field[i]);
		}
		
		for (int i=0; i<X; i++)
		for (int j=0; j<Y; j++)
		{
			if (field[i][j]=='D')
			{
				dX.push_back(i);        //保存所有的门 
				dY.push_back(j);
				bfs(i,j,dist[i][j]);      //bfs求最短路,每次输入一个门都更新dist值 
			}
			else if (field[i][j]=='.')
			{
				pX.push_back(i);        //保存所有的人 
				pY.push_back(j);
			}
		}
		 
		n=X*Y;          //时间上限 
		int d=dX.size(),p=pX.size();
		for (int v=0; v<X*Y*d+p; ++v) G[v].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++)  
					//k从这个人到达这个门的最短时间 一直到最长的时间上限 这个人都可以到达这个门 
						addedge((k-1)*d+i, n*d+j);
					//时间和门的二元组与这个人建边 
				}
			}
		bool oo=0;
		if (p==0)
		{
			printf("0\n");
			oo=1;
		}
		int num = 0;
		
		memset(match, -1, sizeof(match));
		for (int x=0; x<n*d; x++)     //这里的x表示一个时间和门的二元组 
		{
			memset(used, 0,sizeof(used));
			if (dfs(x))       //找到增广路则匹配数加一 
			{
				if (++num == p)        //匹配数等于总人数则在当前时间内可以全部逃出 
				{
					printf("%d\n", x/d+1);  //x/d+1表示当前的时间。 
					oo=1;
				}
			}
		}
		
		if (!oo) printf("impossible\n");  //枚举到时间上限匹配数仍未达到总人数则无法全部逃出 
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值