[網絡流]hdu3046(Dinic)

首先我想講一下網絡流的基礎。
第一,要明白一些基礎的概念,否則對後面的理解會有一定的影響。這裏只列出常用的,列太多會影響閱讀效率的。

底圖:如果把一個有向圖的每條邊的方向都去掉,得到的無向圖稱爲原有圖的底圖。
途徑:圖G中點邊連續交替出現的序列稱爲G的一條途徑。
跡:圖G中邊不重複出現的途徑稱爲跡。
路:圖G中頂點不重複出現的跡稱爲路。

網絡的基本概念:
定義:一個網絡N=(V,A)是指一個連通無環且滿足下列條件的有向圖:
1.有一個定點子集X,其每個頂點的入度都是0;
2.有一個與X不相交的頂點子集Y,其每個頂點的出度都爲0;
3.每條弧都有一個非負的權值,稱爲弧的容量。
上述網絡N記作N(V,X,Y,A,C),其中X爲網絡的源點集,Y稱爲網絡的匯點集,V和A分別稱爲頂點集和弧集。C爲網絡的容量函數。

網絡的可行流:
網絡N=(V,X,Y,A,C)中的一個可行流是指定義在A上的一個整值函數f,使得:
1.對任意a屬於A,0<=f(a)<=c(a)(容量約束);
2.對任意v屬於 V-(XUY),f(v)=f’(v)(流量守恆);
其中f(v)表示點v的入弧的流量之和,f’(v)表示點v的出弧的流量之和。

最大流最小割定理:在任一網絡中,最大流的流量等於最小割的容量。
此定理對於如何求最大流並沒有什麼用處,但是可以用他來證明最大流的正確性。

最大流的求解:
求網絡中的最大流基本思想都是不斷地增加流量,直到不能再增加爲止。這就引入了可增路的概念。
:設u,v是網絡N中任意兩點,P是N的底圖中的一條連接uv的路,若規定P的走向爲從u到v,則稱這樣規定了走向的路P爲網絡N中一條從u到v的路,簡稱u-v路。特別的,一條從源點x到匯點y的路稱爲x-y路。
正向弧就是路中的邊在原圖中是否是正向弧。不是就是反向弧。
可增路:假設f是網絡的一個可行流,u是N中任意一點,P是網絡N中的一條x-u路,如果對路P上的任一弧a,都有:
1.若弧a是P的正向弧,則c(a)-f(a)>0;
2.若弧a是P的反向弧,則f(a)>0;
則P稱爲N的一條可增x-u路。特別的,一條f可增x-y路稱爲 N的一條f可增路。

對於N中任意一條f可增路P和P上任意一條弧a,假設
△f(a)= c(a)-f(a) (正向弧)
f(a) (反向弧)
則可增路的可增加流量爲△f(P)=min{△f(a)};稱爲可增量。
這裏寫圖片描述

最大流Dinic算法:

Dinic算法利用分層的思想對網絡進行一些處理,簡化了一些操作。
1.增量網絡(殘餘網絡)

對於網絡N和N上的可行流f,構造一個新的網絡N(f)=(V,X,Y,A(f),C’)中A(f)及容量函數C’定義如下:

(爲何這麼詭異,把這句好敲出來博客頁面就顯示不出下面的兩張圖和部分文字。。無奈,只好截圖了。。。噗噗噗。。)

2.若(u,v)屬於A並且f(u,v)>0,則(v,u)屬於A(f),並且c’(u,v)=f(u,v);
分層就是bfs一遍掃描網絡,求出每個點到源點的最短距離。
對分層後的網絡進行下一步的操作就可以得到輔助網絡。
輔助網絡:對於增量網絡N(f)進行分層後,刪除層數不低於y的頂點,再刪除從高層指向低層的弧和同層之間的弧。

這裏寫圖片描述

這裏寫圖片描述

其實代碼中並沒有很清晰的看到輔助網絡的身影,直接是bfs分層,dfs計算△f(a),直到bfs不能達到匯點。

好了,說一下本題的題意吧。

題意:就是在一個網格中有些是羊,有兩個是灰太狼和紅太狼,問怎樣用最少的欄杆攔截使小羊安全。

分析:
每個點都有向相鄰點的連線,容量爲1,狼都連向匯點,羊都連向源點,容量爲inf。求最大流。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define read freopen("q.in","r",stdin)
#define maxn 40004
#define inf 0x7fffffff
int edgeNum,n,m;
int d[maxn],head[maxn];

struct Edge
{
    int to,flow,next;
}edge[maxn*4];
int from,to;
void add(int u,int v,int flow)
{
    edge[edgeNum].to=v;edge[edgeNum].flow=flow;edge[edgeNum].next=head[u];head[u]=edgeNum++;
    edge[edgeNum].to=u;edge[edgeNum].flow=0;edge[edgeNum].next=head[v];head[v]=edgeNum++;
}

bool bfs()
{
    memset(d,0,sizeof(d));
    int i,k,j;
    queue<int> q;
    d[from]=1;
    q.push(from);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(!d[v] && edge[i].flow>0)
            {
                d[v]=d[u]+1;
                q.push(v);
                if(v==to)return true;
            }
        }
    }
    return false;
}

int dfs(int u,int flow)
{
    if(u==to || flow==0)return flow;
    int i,j,k;
    int cap=flow;
    for(i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(d[v]==d[u]+1 && edge[i].flow>0)
        {
            int x=dfs(v,min(cap,edge[i].flow));
            cap-=x;
            edge[i].flow-=x;
            edge[i^1].flow+=x;
            if(cap==0)return flow;
        }
    }
    return flow-cap;
}

int dinic()
{
    int sum=0;
    while(bfs())sum+=dfs(from,inf);
    return sum;
}
int main()
{
//  read;
    int i,j,x,cas=1;
    while(~scanf("%d%d",&n,&m))
    {
        from=n*m+1;to=n*m+2;
        edgeNum=0;
        memset(head,-1,sizeof(head));
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                scanf("%d",&x);
                if(x==1)add(from,(i-1)*m+j,inf);
                if(x==2)add((i-1)*m+j,to,inf);
                if(i-1>=1 )add((i-1)*m+j,(i-2)*m+j,1);
                if(j-1>=1 )add((i-1)*m+j,(i-1)*m+j-1,1);
                if(i+1<=n )add((i-1)*m+j,i*m+j,1);
                if(j+1<=m )add((i-1)*m+j,(i-1)*m+j+1,1);

            }
        }
        int res=dinic();
        cout<<"Case "<<cas++<<":\n"<<res<<endl;
    }
}

ps:我覺得講算法附上代碼很重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章