[Luogu 2805]NOI2009 植物大戰僵屍
阿新 • • 發佈:2017-12-19
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 植物大戰僵屍