1. 程式人生 > >[Luogu 2805]NOI2009 植物大戰僵屍

[Luogu 2805]NOI2009 植物大戰僵屍

sca ace clu pty 有效 cst big mem 前向星

<題目鏈接>

這題是個比較經典的最大權閉合子圖,可以建圖轉化為最小割問題,再根據最大流最小割定理,采用任意一種最大流算法求得。

對於每個點,如果點權w為正,則從源點到這個點連一條邊權為w的有向邊;否則如果w為負則從這個點到匯點連一條有向邊。加邊的時候預處理出反圖的每個點入度。

其次,每一個被保護的點到保護它的點連一條邊權為INF的有向邊。

註意同一行的左側點受到右側點的間接保護,因此對於每一個不位於當前行最右的點,都要向其右側的一個點連一條INF有向邊(連一條即可,不必連所有)。

初始化完成後,在反向圖(慣用鏈式前向星的表示,在跑最小割之前,邊權為0的邊就是反向圖的邊)上拓撲排序,刪去在環中的點,留下有效點。

然後在建的這個圖上跑最小割。

最終的答案為:源點到「與源點相連的所有有效點」的邊權和減去網絡最小割。

建模是個好能力,希望包括我在內的每一個OIer都能通過不懈努力獲得。

加油。

#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int MAXN=10000,MAXM=900000;
bool exist[MAXN]/*拓撲排序有效點標記*/,vis[MAXN];
int
n,m,N,S,T,cnt,ans,head[MAXN],cur[MAXN]/*當前弧*/,inn[MAXN]/*反圖入度*/,dis[MAXN]/*層次圖*/; struct edge { int nxt,to,w; }e[MAXM]; void AddEdge(int x,int y,int w) { e[++cnt].nxt=head[x]; e[cnt].to=y; e[cnt].w=w; head[x]=cnt; } void AddEdges(int x,int y,int w) { AddEdge(x,y,w); AddEdge(y,x,0
); ++inn[x];//預處理反圖入度 } void T_Sort(int S) { queue<int> q;//棧或隊列都可以(優先隊列或者手寫堆其實也沒問題) for(int i=S;i<=T;++i) if(!inn[i]) q.push(i);//入度為0的入隊 while(!q.empty()) { int x=q.front(); q.pop(); exist[x]=1; for(int i=head[x],t;i;i=e[i].nxt) if(!e[i].w && !--inn[t=e[i].to]) q.push(t); } for(int i=head[S];i;i=e[i].nxt) if(!inn[e[i].to]) ans+=e[i].w;//事先累加源點到「與源點相連的所有有效點」的邊權 } bool BFS(int S) { queue<int> q; memset(dis,0,sizeof dis); memset(vis,0,sizeof vis); q.push(S); vis[S]=1; while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x],t;i;i=e[i].nxt) if(e[i].w && exist[t=e[i].to] && !vis[t]) { vis[t]=1; dis[t]=dis[x]+1; q.push(t); } if(vis[T])//已經處理到匯點就退出 return 1; } return 0; } int DFS(int x,int k) { if(x==T) return k; int tmp=0;//這個累加必須有,否則最後一個點會被卡掉。 for(int i=cur[x],t,f;i;i=e[i].nxt) if(e[i].w && exist[t=e[i].to] && dis[t]==dis[x]+1 && (f=DFS(t,min(k,e[i].w)))) { cur[x]=i; e[i].w-=f;//當前邊的邊權 e[((i-1)^1)+1].w+=f;//反向邊的邊權 tmp+=f; k-=f; } if(!tmp) dis[x]=-1;//必須有的操作 return tmp; } void Dinic(void) { int f; while(BFS(S))//預處理層次圖,直到無法間出層次圖 while(memcpy(cur,head,sizeof cur),f=DFS(S,INT_MAX))//跑DFS直到當前層次圖上無法增廣。 ans-=f;//連接源點的邊權和減去最小割 } int main(int argc,char *argv[]) { scanf("%d %d",&n,&m); N=n*m,T=N+1;//N是總點數,T是匯點 for(int i=1,w,k;i<=N;++i) { scanf("%d %d",&w,&k); if(i%m)//如果不是當前行最後一個點,就向右連一條邊。 AddEdges(i,i+1,INT_MAX); if(w>0) AddEdges(S,i,w);//連源點 else if(w<0) AddEdges(i,T,-w);//連匯點 for(int j=1,r,c;j<=k;++j) { scanf("%d %d",&r,&c); AddEdges(r*m+c+1,i,INT_MAX);//保護關系 } } T_Sort(S);//拓撲排序 Dinic();//最小割 printf("%d",ans); return 0; }

謝謝閱讀。

[Luogu 2805]NOI2009 植物大戰僵屍