bzoj1189/洛谷3191 [HNOI2007]紧急疏散evacuate【网络流】【二分答案】

题目描述

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

输入输出格式

输入格式:

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

输出格式:

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

一开始网络流时直接将门与汇点建一条mid的边,WA了之后发现有些空位无法到达部分门,果然我还是太年轻。

应该将门按照时间拆成mid个点然后dinic判断最大流是否为人数。

上代码:

#include<queue>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
const int xx[4]={0,1,0,-1},yy[4]={1,0,-1,0};
const int maxn=4e4+5,maxm=2e6+5,INF=0x3f3f3f3f;
char mp[25][25];
int N,M,S,T,ans,tot,sum,st,cnt,hed,til,L,R;
int son[maxm],nxt[maxm],flw[maxm],lnk[maxn],G[25][25];
int dep[maxn],cur[maxn],que[maxn],ed[maxn],dis[maxn][25][25];
inline char read() {
	char ch=getchar();
	while (ch!='X'&&ch!='.'&&ch!='D') ch=getchar();
	return ch;
}
inline bool bfs() {
	for (int i=0; i<=T; i++) cur[i]=lnk[i],dep[i]=-1;
	que[til=1]=S,hed=dep[S]=0;
	while (hed<til)
		for (int k=lnk[que[++hed]]; ~k; k=nxt[k]) if (dep[son[k]]<0&&flw[k]>0)
			dep[son[k]]=dep[que[hed]]+1,que[++til]=son[k];
	return dep[T]>0;
}
int dfs(int now,int flow) {
	if (now==T) return flow;
	for (int &k=cur[now]; ~k; k=nxt[k]) if (dep[son[k]]==dep[now]+1&&flw[k]) {
		int d=dfs(son[k],min(flow,flw[k]));
		if (d>0) {
			flw[k]-=d,flw[k^1]+=d;
			return d;
		}
	}
	return 0;
}
inline void add_edge(int x,int y,int z) {
	son[tot]=y,flw[tot]=z,nxt[tot]=lnk[x],lnk[x]=tot++;
	son[tot]=x,flw[tot]=0,nxt[tot]=lnk[y],lnk[y]=tot++;
}
inline bool check(int x) {
	S=0,T=maxn-1;
	memset(lnk,-1,sizeof lnk),tot=0;
	for (int i=1; i<=sum; i++) add_edge(S,i,1);
	for (int k=1; k<=cnt; k++)
		for (int i=0; i<N; i++)
			for (int j=0; j<M; j++) if (mp[i][j]=='.'&&dis[k][i][j]<=x)
				add_edge(G[i][j],(k-1)*x+sum+dis[k][i][j],1);
	for (int i=1; i<=cnt; i++)
		for (int j=1; j<=x; j++) {
			int tmp=(i-1)*x+sum;
			add_edge(tmp+j,T,1);
			if (j<x) add_edge(tmp+j,tmp+j+1,INF);
		}
	int ret=0;
	while (bfs()) while (int d=dfs(S,INF)) ret+=d;
	return ret==sum;
}
inline void pre(int id) {
	queue <int> Q;
	while (!Q.empty()) Q.pop();
	dis[id][ed[id]/M][ed[id]%M]=0;
	Q.push(ed[id]);
	while (!Q.empty()) {
		int now=Q.front(),x=now/M,y=now%M;
		Q.pop();
		for (int f=0; f<4; f++) {
			int l=x+xx[f],r=y+yy[f];
			if (l<0||r<0||l>=N||r>=M||mp[l][r]!='.') continue;
			if (dis[id][l][r]>dis[id][x][y]+1)
				dis[id][l][r]=dis[id][x][y]+1,Q.push(l*M+r);
		}
	}
}
int main() {
	memset(dis,63,sizeof dis);
	scanf("%d%d",&N,&M);
	for (int i=0; i<N; i++)
		for (int j=0; j<M; j++) {
			mp[i][j]=read();
			if (mp[i][j]=='D') ed[++cnt]=i*M+j;
			if (mp[i][j]=='.') G[i][j]=++sum;
		}
	for (int i=1; i<=cnt; i++) pre(i);
	L=1,R=N*M;
	if (!check(R)) {puts("impossible");return 0;}
	while (L<=R) {
		int mid=L+(R-L>>1);
		if (check(mid)) ans=mid,R=mid-1;
		else L=mid+1;
	}
	printf("%d\n",ans);
	return 0;
}

这是我造数据的代码,思路是造门的位置,然后bfs造空位:

#include<windows.h>
#include<bits/stdc++.h>
using namespace std;
const int xx[4]={0,1,0,-1},yy[4]={1,0,-1,0};
int N,M,mp[950][950],X[1000],Y[1000],tot,sum,hed,til;
struct point{int x,y;};
inline int random(int x) {
	return ((rand()<<16)+(rand()<<1)+(rand()&1))%x+1;
}
inline int bfs(int id,int sum) {
	queue <point> Q;
	while (!Q.empty()) Q.pop();
	point st;
	st.x=X[id],st.y=Y[id];
	Q.push(st);
	while (!Q.empty()) {
		if (sum<=0) break;
		point now=Q.front(),nxt;
		Q.pop();
		for (int f=0; f<4; f++) if (random(15)^1) {
			int l=now.x+xx[f],r=now.y+yy[f];
			if (l<2||r<2||l>=N||r>=M||mp[l][r]!=3) continue;
			sum--,mp[l][r]=1;
			nxt.x=l,nxt.y=r;
			Q.push(nxt);
		}
	}
}
int main() {
	freopen("OJ.in","w",stdout);
	srand(GetTickCount());
	N=20,M=20;
	tot=rand()%10+2;
	printf("%d %d\n",N,M);
	for (int i=1; i<=N; i++)
	for (int j=1; j<=M; j++) mp[i][j]=3;
	for (int i=1; i<=tot; i++) {
		int x=rand()%N+1,y=rand()%M+1;
		if (mp[x][y]==3) mp[x][y]=2,X[++sum]=x,Y[sum]=y;
	}
	for (int i=1; i<=tot; i++) bfs(i,rand()%70);
	for (int i=1; i<=N; i++,putchar('\n'))
	for (int j=1; j<=M; j++)
		switch (mp[i][j]) {
			case 1: putchar('.');break;
			case 2: putchar('D');break;
			case 3: putchar('X');break;
		}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值