[BZOJ1189][HNOI2007]紧急疏散evacuate

BZOJ
Luogu
以后不放题面了自己看去

sol

显然时间是二分的对吧。
我们二分一个时间,假设为\(mid\),那么就要把每扇门拆成\(mid\)个点,各自向汇点连容量为1的边,表示每一扇门在一个单位时间里可以让一个人逃出。
\(BFS\)预处理出每个人到每扇门的距离,然后如果这个人在\(mid\)时间内可以到达这扇门就向这扇门对应的时间点连一条容量为1的边。
源点向每个人连容量为1的边,然后二分判断最大流是否等于总人数。
但是!
如果同一时间有多个人到达了同一扇门,然后其中一个人发挥谦让精神让另一个人先走了,自己在门这里等了一个单位时间才走的,那我们怎么处理呢?
很简单。从每扇门对应时间\(t\)的点向\(t+1\)的点连一条\(inf\)的边,就可以解决这种问题了。

code

细节。。。还好吧
一些需要注意的点在代码中已经用注释标出

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 40;
const int inf = 1e9;
int n,m,P[N][N],dis[N<<2][N][N],door,people;
char g[N][N];
struct node{int x,y;};
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
queue<int>Q;
queue<node>QQ;
void BFS(int k,int p,int q)//第k扇门,它的位置是(p,q)
{
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			dis[k][i][j]=inf;
	dis[k][p][q]=0;QQ.push((node){p,q});
	while (!QQ.empty())
	{
		int x=QQ.front().x,y=QQ.front().y;QQ.pop();
		for (int d=0;d<4;d++)
		{
			int i=x+dx[d],j=y+dy[d];
			if (i<1||i>n||j<1||j>m||g[i][j]!='.') continue;//这里如果写成" g[i][j]=='X' "的话在BZOJ上会WA(但是洛谷可以AC)
			if (dis[k][i][j]>dis[k][x][y]+1)
				dis[k][i][j]=dis[k][x][y]+1,QQ.push((node){i,j});
		}
	}
}
struct edge{int to,next,w;}a[N*N*N*N];
int S,T,head[N*N*N],cnt,dep[N*N*N],cur[N*N*N];
void link(int u,int v,int w)
{
    a[++cnt]=(edge){v,head[u],w};
    head[u]=cnt;
    a[++cnt]=(edge){u,head[v],0};
    head[v]=cnt;
}
bool bfs()
{
    memset(dep,0,sizeof(dep));
    dep[S]=1;Q.push(S);
    while (!Q.empty())
    {
        int u=Q.front();Q.pop();
        for (int e=head[u];e;e=a[e].next)
            if (a[e].w&&!dep[a[e].to])
                dep[a[e].to]=dep[u]+1,Q.push(a[e].to);
    }
    return dep[T];
}
int dfs(int u,int flow)
{
    if (u==T)
        return flow;
    for (int &e=cur[u];e;e=a[e].next)
        if (a[e].w&&dep[a[e].to]==dep[u]+1)
        {
            int temp=dfs(a[e].to,min(flow,a[e].w));
            if (temp) {a[e].w-=temp;a[e^1].w+=temp;return temp;}
        }
    return 0;
}
int Dinic()
{
    int res=0;
    while (bfs())
    {
        for (int i=T;i;i--) cur[i]=head[i];
        while (int temp=dfs(S,inf)) res+=temp;
    }
    return res;
}
int check(int mid)
{
	S=door*mid+people+1;T=S+1;
	memset(head,0,sizeof(head));cnt=1;
	for (int k=1;k<=door;k++)
		for (int t=1;t<=mid;t++)
		{
			link((k-1)*mid+t,T,1);
			if (t<mid) link((k-1)*mid+t,(k-1)*mid+t+1,inf);
		}
	for (int i=1;i<=people;i++)
		link(S,door*mid+i,1);
	for (int k=1;k<=door;k++)
		for (int i=1;i<=n;i++)
			for (int j=1;j<=m;j++)
				if (g[i][j]=='.'&&dis[k][i][j]<=mid)//这里一定要写" g[i][j]=='.' ",不然就会把自己这扇门的那个位置也算进去(就这样洛谷上还跑90分?)
					link(door*mid+P[i][j],(k-1)*mid+dis[k][i][j],1);
	return Dinic();
}
int main()
{
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%s",g[i]+1);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			if (g[i][j]=='D')
				BFS(++door,i,j);
			else if (g[i][j]=='.')
				P[i][j]=++people;
	int l=0,r=people+1;
	while (l<r)
	{
		int mid=l+r>>1;
		if (check(mid)==people) r=mid;
		else l=mid+1;
	}
	if (l==people+1) puts("impossible");
	else printf("%d\n",l);
	return 0;
}
posted @ 2018-01-04 17:48  租酥雨  阅读(394)  评论(1编辑  收藏  举报