[BZOJ1189/Luogu3191][HNOI2007]紧急疏散EVACUATE

题目链接:

BZOJ1189

Luogu3191

\(Duliu\)网络流

首先,很容易看出二分答案+网络流的方法:

二分答案,将每一扇门拆成相应个数的节点。

对于每一个人,与源点连边,流量为\(1\),代表一个人。

然后对于每个人跑一遍最短路(边权均为\(1\),直接\(BFS\)),向\(Ta\)能够到达的门相应时间连边,容量为\(1\)

每一扇门的每个时间节点向汇点连边,容量为\(1\),表示能够通过\(1\)人。

于是一边吐槽水题,一边愉快的写了起来,然后发现\(TLE\)了。

怎么优化呢?改善建图方式。

对于每一扇门,时间\(i\)的节点向\(i+1\)连边,容量为\(+\infty\),表示不能走这一扇就走下一扇,然后对于每个人,向\(Ta\)第一时间能够到达的节点连边即可,容量为\(1\)

这么做以后,就跑得飞快了。

话说这题好难写。。

#include <queue>
#include <cstdio>
#include <cstring>

inline int Min(int a,int b){return a<b?a:b;}
inline int Max(int a,int b){return a>b?a:b;}

int n,m,Pec,Doc,St,Ed;
int Ds[25][25][25][25],IDS[25][25][205],IDC;
//Ds[i][j][x][y]为(i,j)到(x,y)的最短距离
//IDS用于离散化节点编号
int Head[130005],Next[200005],To[200005],Val[200005],En;
int Dis[130005],Cnt[130005],Cur[130005],Pre[130005];
char Map[25][25];
struct Point{int x,y;};
const int nx[]={-1,1,0,0},ny[]={0,0,-1,1};

inline int ID(int x,int y,int z)
{
	if(!IDS[x][y][z])IDS[x][y][z]=++IDC;
	return IDS[x][y][z];
}

inline void Add(int x,int y,int z)
{
	Next[++En]=Head[x],To[Head[x]=En]=y,Val[En]=z;
	Next[++En]=Head[y],To[Head[y]=En]=x,Val[En]=0;
}

void BFS(int Sx,int Sy)//计算(Sx,Sy)到其它节点的距离
{
	int (*f)[25]=Ds[Sx][Sy];
	std::queue<Point> q;
	q.push((Point){Sx,Sy});
	memset(f,0x3f,sizeof(int)*25*25);
	f[Sx][Sy]=0;
	for(;!q.empty();q.pop())
	{
		int x=q.front().x,y=q.front().y;
		for(int i=0;i<4;++i)
		{
			int wx=x+nx[i],wy=y+ny[i];
			if(wx<1||wx>n||wy<1||wy>m)continue;
			if(f[wx][wy]<=f[x][y]+1||Map[wx][wy]=='X')continue;
			f[wx][wy]=f[x][y]+1;
			if(Map[wx][wy]=='.')q.push((Point){wx,wy});//门1s只能通过一人
		}
	}
}

void Build(int Tim)//建图,时间为Tim
{
	memset(Head,0,sizeof Head),En=1;
	memset(IDS,IDC=0,sizeof IDS);
	St=n*m+Tim*Doc+1,Ed=St+1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			if(Map[i][j]=='D')
				for(int k=1;k<=Tim;++k)
				{
					Add(ID(i,j,k),Ed,1);
					if(k<Tim)Add(ID(i,j,k),ID(i,j,k+1),0x3f3f3f3f);//向后一条节点连边,容量Inf
				}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			if(Map[i][j]=='.')
			{
				Add(St,ID(i,j,1),1);
				for(int x=1;x<=n;++x)
					for(int y=1;y<=m;++y)
						if(Map[x][y]=='D')
							if(Ds[i][j][x][y]<=Tim)
								Add(ID(i,j,1),ID(x,y,Ds[i][j][x][y]),1);//向第一个门连边。
			}
}

int Augment()
{
	int Res=0x3f3f3f3f;
	for(int x=Ed;x!=St;x=To[Pre[x]^1])Res=Min(Res,Val[Pre[x]]);
	for(int x=Ed,i;x!=St;x=To[i^1])Val[i=Pre[x]]-=Res,Val[i^1]+=Res;
	return Res;
}

int ISAP()//简单的网络流
{
	memset(Dis,0,sizeof Dis);
	memcpy(Cur,Head,sizeof Cur);
	memset(Cnt,0,sizeof Cnt);
	Cnt[0]=Ed;
	int Flow=0,x=St;
	while(Dis[St]<Ed)
	{
		if(x==Ed)Flow+=Augment(),x=St;
		bool Flag=false;
		for(int i=Cur[x],y;i;i=Next[i])
			if(Val[i]&&Dis[y=To[i]]+1==Dis[x])
				Flag=true,Cur[x]=Pre[y]=i,x=y,i=0;
		if(Flag)continue;
		int Wd=Ed-1;
		for(int i=Head[x];i;i=Next[i])
			if(Val[i])Wd=Min(Wd,Dis[To[i]]);
		if(!--Cnt[Dis[x]])break;
		++Cnt[Dis[x]=Wd+1],Cur[x]=Head[x];
		if(x!=St)x=To[Pre[x]^1];
	}
	return Flow;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",Map[i]+1);
		for(int j=1;j<=m;++j)
			if(Map[i][j]=='.')++Pec;//人的数量
			else if(Map[i][j]=='D')++Doc;//门的数量
	}
	if(!Pec)return puts("0"),0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			BFS(i,j);
	int l=0,r=202;
	while(l<r)
	{
		int Mid=(l+r)>>1;
		Build(Mid);
		if(ISAP()==Pec)r=Mid;
		else l=Mid+1;
	}
	if(l>200)puts("impossible");
	else printf("%d\n",l);
	return 0;
}
posted @ 2018-12-27 19:34  LanrTabe  阅读(188)  评论(0编辑  收藏  举报