bzoj 1189: [HNOI2007]紧急疏散evacuate

1189: [HNOI2007]紧急疏散evacuate

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 1817   Solved: 605
[ Submit][ Status][ Discuss]

Description

发生了火警,所有人员需要紧急疏散!假设每个房间是一个N M的矩形区域。每个格子如果是'.',那么表示这是一块空地;如果是'X',那么表示这是一面墙,如果是'D',那么表示这是一扇门,人们可以从这儿撤出房间。已知门一定在房间的边界上,并且边界上不会有空地。最初,每块空地上都有一个人,在疏散的时候,每一秒钟每个人都可以向上下左右四个方向移动一格,当然他也可以站着不动。疏散开始后,每块空地上就没有人数限制了(也就是说每块空地可以同时站无数个人)。但是,由于门很窄,每一秒钟只能有一个人移动到门的位置,一旦移动到门的位置,就表示他已经安全撤离了。现在的问题是:如果希望所有的人安全撤离,最短需要多少时间?或者告知根本不可能。

Input

输入文件第一行是由空格隔开的一对正整数N与M,3<=N <=20,3<=M<=20,以下N行M列描述一个N M的矩阵。其中的元素可为字符'.'、'X'和'D',且字符间无空格。

Output

只有一个整数K,表示让所有人安全撤离的最短时间,如果不可能撤离,那么输出'impossible'(不包括引号)。

Sample Input

5 5
XXXXX
X...D
XX.XX
X..XX
XXDXX

Sample Output

3

HINT

2015.1.12新加数据一组,鸣谢1756500824


C++语言请用scanf("%s",s)读入!

Source

[ Submit][ Status][ Discuss]


题解:二分答案+最大流

刚开始想用费用流,但是发现费用流没法处理两个人在同一时间到达,一个人需要再等一秒钟的情况,于是改成了最大流。

先预处理出每个'.'到每个门的最短距离,然后二分一个最短时间mid

从源点向所有的'.'点连一条容量为1的边,因为每个位置最初有一个人。

然后把每个门拆成mid个时间点,从这mid个点在向这个门所代表的点连一条容量mid-x+1的边(x表示到达该门的时间,为什么呢不妨想一下,如果有两个人在2这个时间点到达,当前的总时限为2,因为有一个人一定需要等待,即需要在第3秒出门,所以加了这个容量限制,就能保证在时间内不会有人多出去),然后再从门所代表的点向汇点连一条容量为mid的边(因为在mid时间内,出门的人数不可能超过mid)。

如果某个人的某个门的距离<=mid,那么就从这个人向门拆成的点代表的当前所需时间的点连边。

大体方式如下图:


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 100000
#define inf 1000000000
using namespace std;
int dis[N],next[N],point[N],v[N],remain[N];
int n,m,k,tot=-1,head,tail,maxflow,mincost,mid,cnt;
int last[N],can[N],a[100][100],f[33][33][33][33],vis[33][33];
int mark[33][33],sz,ch[432][3],deep[N],cur[N],num[N];
int x[10]={0,1,0,-1},y[10]={1,0,-1,0};
int q1[N],q2[N];
char s[100];
void add(int x,int y,int z)
{
   tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; 
   tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; 	
}
int addflow(int s,int t)
{
	int now=t; int ans=inf;
	while (now!=s)
	{
		ans=min(ans,remain[last[now]]);
		now=v[last[now]^1];
	}
    now=t;
    while (now!=s)
     {
     	remain[last[now]]-=ans;
     	remain[last[now]^1]+=ans;
     	now=v[last[now]^1];
     }
    return ans;
}
void bfs(int s,int t)
{
	for (int i=s;i<=t;i++)
	 deep[i]=t;
	deep[t]=0; 
	queue<int> p; p.push(t);
	while (!p.empty())
	 {
	 	int now=p.front(); p.pop();
	 	for (int i=point[now];i!=-1;i=next[i])
	      if (deep[v[i]]==t&&remain[i^1])
	       deep[v[i]]=deep[now]+1,p.push(v[i]);
	 }
}
bool isap(int s,int t)
{
	bfs(s,t);
	for (int i=s;i<=t;i++)
	 cur[i]=point[i];
	for (int i=s;i<=t;i++)
	 num[deep[i]]++;
	int now=s; int ans=0;
	while (deep[s]<t)
	{
		if (now==t)
		 {
		 	ans+=addflow(s,t);
		 	now=s;
		 }
		bool f=false;
		for (int i=cur[now];i!=-1;i=next[i])
		 if (deep[v[i]]+1==deep[now]&&remain[i])
		  {
		  	last[v[i]]=i;
		  	f=true;
		  	cur[now]=i;
		  	now=v[i];
		  	break;
		  }
		if(!f)
		{
			int minn=t;
			for (int i=point[now];i!=-1;i=next[i])
			 if (remain[i])  minn=min(deep[v[i]],minn);
			if (!--num[deep[now]]) break;
			deep[now]=minn+1;
			num[deep[now]]++;
			cur[now]=point[now];
			if (now!=s)
			 now=v[last[now]^1];
		}
	}
	return ans==cnt;
}
void Bfs(int xl,int yl)
{
	memset(vis,0,sizeof(vis));
	int l=0; int r=1;
	q1[r]=xl; q2[r]=yl;
	f[xl][yl][xl][yl]=0;
	vis[xl][yl]=1;
	while (l!=r)
	 {
	 	l++;
	 	int nowx=q1[l]; int nowy=q2[l]; int t=f[nowx][nowy][xl][yl];
	 	for (int i=0;i<4;i++)
	 	 {
	 	 	int xx=nowx+x[i]; int yy=nowy+y[i];
	 	 	if (xx>0&&yy>0&&xx<=n&&yy<=m&&a[xx][yy]==1&&!vis[xx][yy])
	 	 	  {
	 	 	  	f[xx][yy][xl][yl]=t+1;
	 	 	  	vis[xx][yy]=1;
	 	 	  	r++; q1[r]=xx; q2[r]=yy;
	 	 	  }
	 	 }
	 }
}
int build()
{
	tot=-1; 
	memset(point,-1,sizeof(point));
	memset(next,-1,sizeof(next));
	int num=cnt+1+sz*mid;
	for (int i=1;i<=n;i++)
	 for (int j=1;j<=m;j++)
	  {
	  	if (a[i][j]==1)
	  	 {
	  	 	add(1,mark[i][j]+1,1);
	  	 	for (int k=1;k<=sz;k++)
	  	 	 {
	  	 	 	int xx=ch[k][0]; int yy=ch[k][1];
	  	 	 	if (f[i][j][xx][yy]&&f[i][j][xx][yy]<=mid)
	  	 	 	 {
	  	 	 	 	int t=cnt+1+(mark[xx][yy]-1)*mid+f[i][j][xx][yy];
	  	 	 	 	add(mark[i][j]+1,t,1);
	  	 	 	 }
	  	 	 }
	  	  }
	  	 if (a[i][j]==2)
	  	  {
	  	  	for (int k=1;k<=mid;k++)
	  	  	 {
	  	  	 	int t=cnt+1+(mark[i][j]-1)*mid+k;
	  	  	 	add(t,num+mark[i][j],mid-k+1);
	  	  	 }
	  	  	add(num+mark[i][j],num+sz+1,mid);
	  	  }
	  }
	 return num+sz+1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	 {
	 	scanf("%s",s+1);
	 	for (int j=1;j<=m;j++)
	 	 if (s[j]=='.')  a[i][j]=1,cnt++,mark[i][j]=cnt;
	 	 else  if (s[j]=='D') a[i][j]=2;
	 	 else  if (s[j]=='X') a[i][j]=0;
	 }
	for (int i=1;i<=n;i++)
	 for (int j=1;j<=m;j++)
	  if (a[i][j]==2)
	  { 
	    Bfs(i,j),mark[i][j]=++sz,ch[sz][0]=i,ch[sz][1]=j;
	  }
	head=1; tail=m*n;
	int ans=0;
	while (head<=tail)
	 {
	 	mid=(head+tail)/2;
	 	int t=build();
	 	if (isap(1,t))
	 	 ans=mid,tail=mid-1;
	 	else
	 	 head=mid+1;
	 }
	if (!ans)
	 printf("impossible\n");
	else
	 printf("%d\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值