1. 程式人生 > >[USACO4.2]草地排水Drainage Ditches

[USACO4.2]草地排水Drainage Ditches

傳送門

這個題幾乎可以用來作為最大流入門的題了,沒有任何掩飾,直接告訴你這是最大流。
這裡解釋一下網路流的建反向邊操作,這其實是一個退流操作,拿下面一個圖舉個例子:

如果最先走了中間那條邊,我們這個圖就會變成這樣:

此時我們發現那條反向邊能被另一條路徑經過,但是這樣不是錯的嗎,因為實際上這條路徑是不存在的,讓我們走走試試:

誒,這不就等價於上面那條路走掉了50流量,下面那條路也走掉了50流量,讓我們再仔細思考一下:中間那條邊的狀態又和最初一樣了,就好像沒有走過。
所以反向邊走掉多少流量就等於退回正向邊多少流量,然後我們就能發現這個做法顯然是正確的。
自我認為沒有什麼別的難理解的地方了。
程式碼(dinic):

#include<cstdio>
#include<queue>
#include<cstring>
#define min(a,b) (a<b?a:b)
int dis[201],ans,n,m,cnt=1,s,t,inf=1e9+7,pre[401],nxt[401],h[201],v[401];std::queue<int>q;
void add(int x,int y,int z)
{
    pre[++cnt]=y,nxt[cnt]=h[x],h[x]=cnt,v[cnt]=z;
    pre[++cnt]=x,nxt[cnt]=h[y],h[y]=cnt;
}
bool bfs()
{
    memset(dis,0,sizeof dis);
    q.push(s),dis[s]=1;
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=h[x];i;i=nxt[i])if(!dis[pre[i]]&&v[i])dis[pre[i]]=dis[x]+1,q.push(pre[i]);
    }
    return dis[t];
}
int dfs(int x,int flow)
{
    if(x==t||!flow)return flow;
    int f=flow;
    for(int i=h[x];i;i=nxt[i])
        if(v[i]&&dis[pre[i]]>dis[x])
        {
            int y=dfs(pre[i],min(v[i],f));
            f-=y,v[i]-=y,v[i^1]+=y;
            if(!f)return flow;
        }
    if(f==flow)dis[x]=-1;//這是一個優化,正確性顯然 
    return flow-f;
}
int main()
{
    scanf("%d%d",&n,&m);s=1,t=m;
    for(int i=1,x,y,z;i<=n;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z);
    for(;bfs();ans+=dfs(s,inf));
    printf("%d\n",ans);
}