1. 程式人生 > >bzoj 1189 二分+最大流

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 MB
Submit: 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

Sample Output

3