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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章