bzoj 1189 二分+最大流
思路:
先预处理出每个人到每扇门的时间,用门作为起点进行bfs处理。
然后二分时间,假设时间为x,将每扇门拆成1到x,x个时间点,表示这扇门有几个时间点是可以出去的。对于一扇门,每个时间点都向后一个时间点建边,表示人在当前时间点到达,可以在下一时间点出去。
先将s连上所有的空地,流量为1,建立每个空地每个门的对应的时间点流量为1的边,表示这个空地的人会再某一时间点到达这扇门。然后每个门流向t,流量为inf。只要最大流为空地的数量,则代表该时间是可以的,继续向下二分。
#include<bits/stdc++.h> #define CLR(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; int fx[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; int dis[100][30][30],n,m; char s[30][30]; int ed[1000],g[30][30],cnt,sum,st,t; const int maxn=40010; const int inf=0x3f3f3f3f; int head[maxn],tot=0; struct edge{ int to,f,Next; }a[maxn<<1]; void addv(int u,int v,int w){ a[++tot].to=v,a[tot].f=w,a[tot].Next=head[u],head[u]=tot; a[++tot].to=u,a[tot].f=0,a[tot].Next=head[v],head[v]=tot; } inline void bfs(int id){ queue<int >q; dis[id][ed[id]/m][ed[id]%m]=0; q.push(ed[id]); while(!q.empty()){ int u=q.front(); int x=u/m,y=u%m; q.pop(); for(int i=0;i<4;i++){ int xx=x+fx[i][0],yy=y+fx[i][1]; if(xx<0||xx>=n||yy<0||yy>=m||s[xx][yy]!='.')continue; if (dis[id][xx][yy]>dis[id][x][y]+1) dis[id][xx][yy]=dis[id][x][y]+1,q.push(xx*m+yy); } } } int deep[maxn]; bool vis[maxn]; inline void find(){ CLR(deep,0); queue<int >q; vis[st]=1; q.push(st); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=a[i].Next) { if(a[i].f && !vis[a[i].to]){ q.push(a[i].to); deep[a[i].to]=deep[u]+1; vis[a[i].to]=1; } } } } int dfs(int u,int delta){ if(u==t)return delta; int ret=0; for(int i=head[u];delta&&i!=-1;i=a[i].Next){ if(a[i].f&&deep[a[i].to]==deep[u]+1){ int dd=dfs(a[i].to,min(a[i].f,delta)); a[i].f-=dd; a[i^1].f+=dd; delta-=dd; ret+=dd; } } return ret; } inline bool check(int x){ st=0,t=maxn-2,tot=1; CLR(head,-1); for(int i=1;i<=sum;i++)addv(st,i,1); for(int k=1;k<=cnt;k++) for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(s[i][j]=='.'&&dis[k][i][j]<=x) addv(g[i][j],sum+(k-1)*x+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; addv(tmp+j,t,1); if (j<x) addv(tmp+j,tmp+j+1,inf); } int ret=0; while(1){ CLR(vis,0); find(); if(!vis[t]){ return ret==sum; } ret+=dfs(st,inf); } } int main(){ cin>>n>>m; for(int i=0;i<n;i++) { scanf("%s",s[i]); } for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { if(s[i][j]=='D'){ ed[++cnt]=i*m+j; } if(s[i][j]=='.')g[i][j]=++sum; } } CLR(dis,0x3f); for(int i=1;i<=cnt;i++) { bfs(i); } int l=1,r=n*m,mid; int ans; if(!check(r)){ puts("impossible"); return 0; } while(l<=r){ mid=(l+r)>>1;//printf("l:%d r:%d mid:%d\n",l,r,mid); if(check(mid)){ ans=mid,r=mid-1; // printf("ans:%d\n",ans); } else l=mid+1; } cout<<ans<<endl; }
1189: [HNOI2007]紧急疏散evacuate
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 3825 Solved: 1118
[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
XXXXX
X...D
XX.XX
X..XX
XXDXX
Sample Output
3
——愿为泰山而不骄
qq850874665~~