1. 程式人生 > 其它 >網路流複習

網路流複習

網路流建圖總結

1.最小割

模型轉化:原題求最小代價,則直接設割掉的是需要選擇的。若原題求最大收益,則設割掉的是不選擇的,最後用總和減去最小割就是答案。

1.1割的性質

  • 性質1:(不連通)在給定的流網路中,去掉割的邊集,則不存在任何一條從源到匯的路徑。
  • 性質2:(兩類點)在給定的流網路中,任意一個割將點集劃分成兩部分。割為兩部分點之前的“橋樑”。

1.2技巧

  • 用正無限容量排除不參與決策的邊。
  • 使用割的定義式來分析最優性
  • 利用源或匯關聯的邊容量處理點權
  • 最小割和最大流是對偶問題,可以將問題的性質從邊轉化為點,從點轉化為邊

1.3最大閉合權圖(選擇了一些點必須要選擇另一些點,偏序關係等)

1.3.1定義

  • 閉合圖定義:定義個一有向圖\(G=(V,E)\)閉合圖是該有向圖的一個點集,且該點集的所有出邊都還指向該點集。即閉合圖內的任意點的任意後繼也一定在閉合圖中。形式化定義就是:閉合圖是這樣的一個點集\(V^{'}\in V\),滿足對於\(\forall u\in V^{'}\)引出的\(\forall <u,v>\in E\),必有\(v\in V^{'}\)成立。還有一種定義:滿足對於\(\forall <u,v>\in E\),若有\(u\in V^{'}\),必有\(v\in V^{'}\)成立。
  • 最大閉合權圖:給每個點\(v\)分配一個點權\(w_v\)(可正可負)。最大權閉合圖是一個點權之和最大的閉合圖,即最大化\(\mathop{\sum}\limits_{v\in V^{'}}w_v\)

1.3.2應用方法

給出的圖一般是一個有向圖,一個閉合圖可以看做是一些點具有相互依賴的關係。因此對於有依賴關係,並且題目可以轉化成給某些點賦權為正,某些點賦權為負的有依賴求最大權值和問題,考慮用最大閉合權圖。

1.3.3構造

在原圖的基礎上增加源點\(s\)和匯點\(t\);將原圖中的每條有向邊\(<u,v>\in E\)替換成容量為\(c(u,v)=\infty\)的有向邊\(<u,v>\in E{'}\);增加連線源\(s\)到原圖的每個正點權\(v(w_v>0)\)的有向邊\(<s,v>\in E^{'}\),容量為\(c(s,v)=w_v\)

;增加連線源\(t\)到原圖的每個負點權\(v(w_v<0)\)的有向邊\(<v,t>\in E^{'}\),容量為\(c(s,v)=-w_v\)。建圖時對應的反向邊容量均為\(0\)

最後的答案就等於原圖所有正點權的和減去最小割,即\(ans=\mathop{\sum}\limits_{v\in V^{+}}w_v-c[S,T]\)

由於原圖中的邊容量為無窮,因此不會出現在最小割中,因此最後的割邊一定是一組簡單割。即每條割邊都只和\(s\)關聯或\(t\)關聯。

1.4 最大密度子圖

1.4.1定義:

  • 定義一個無向圖\(G=(V,E)\)密度\(D\)為該圖的邊數\(|E|\)與該圖的點數\(|V|\)的比值\(D=\frac{|E|}{|V|}\)。給出一個無向圖\(G\),其具有最大密度的子圖\(G^{'}=(V^{'},E^{'})\)稱為最大密度子圖,即最大化\(D^{'}=\frac{E^{'}}{V^{'}}\)。一般是無向圖

1.4.2 構造

解決方法是二分答案,假設當前二分的答案為\(g\),原圖的邊數為\(M\),那麼建圖方式如下:對原圖中的所有邊\(<u,v>\in E\),建立容量為\(1\)的邊\(<u,v>\in E{'}\),其反向邊容量也為\(1\);建議源點\(s\)匯點\(t\)\(s\)向原圖中的每個點\(v\)連線容量為\(M\)的邊,反向邊容量為\(0\);原圖中每個點\(v\)\(t\)連線正向容量為\(M+2g-degree[v]\),發現容量為\(0\)的邊。

void add(int u,int v,double c1,double c2)
{
    e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=c1,head[u]=cnt++;
    e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=c2,head[v]=cnt++;
}
void build(double g)
{
    memset(head,-1,sizeof head);
    cnt=0;
    for(int i=1;i<=m;i++) add(edge[i].a,edge[i].b,1,1);
    for(int i=1;i<=n;i++)
    add(S,i,m,0),add(i,T,m+2*g-dg[i],0);
}
double dinic(double g)
{
    build(g);
    double res=0,flow;
    while(bfs()) while(flow=find(S,inf)) res+=flow;
    return res;
}
int main()
{
    cin>>n>>m;
    S=0,T=n+1;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        edge[i]={a,b};
        dg[a]++,dg[b]++;
    }
    double l=0,r=m;
    while(l+1e-8<r)
    {
        double mid=(l+r)/2;
        double t=dinic(mid);
        if(m*n-t>0) l=mid;
        else r=mid;
    }
    dinic(l);
}

1.5 最小點權覆蓋集與最大點權獨立集

1.5.1定義

  • 點覆蓋集:是無向圖\(G\)的一個點集,使得該圖中所有邊都至少有一個端點在該集合中。形式化定義就是點覆蓋集\(V^{'}\in V\),滿足對於\(\forall (u,v)\in E\),都有\(u\in V^{'}\)\(v\in V^{'}\)至少一個成立。
  • 點獨立集:是無向圖\(G\)的一個點集,使得任意兩個在該集合中的點在原圖中都不相鄰。形式化定義就是點獨立集為\(V^{'}\in V\),滿足對於\(\forall (u,v)\in E\),都有\(u\in V^{'}\)\(v\in V^{'}\)不同時成立。
  • 最小點覆蓋集:點數最少的點覆蓋集
  • 最大點獨立集:點數最多的點獨立集

以上問題都可以用二分圖的最大匹配模型轉化解決。

  • 最小點權覆蓋集:點權之和最小的點覆蓋集
  • 最大點權獨立集:點權之和最大的點獨立集

1.5.2最小點權覆蓋集

  • 分析:建立一個源點\(s\)和匯點\(t\)\(s\)向每個\(X\)部連邊,\(t\)向每個\(Y\)部連邊,把二分圖中的邊看成是有向的。則任意一條從\(s\)\(t\)的路徑,一定具有\(s-u-v-t\)的形式。割的性質是不存在一條從s到t的路徑,故路徑上的三條邊\(<s,u>,<u,v>,<v.t>\)中至少有一條邊在割邊中。利用技巧1,我們將$c(u,v)=\infty \(,那麼問題轉為為\)<s,u>\(和\)<v,t>\(中至少一條在最小割,正好與點覆蓋集限制條件符合(\)u\in V^{'},v\in V^{'}$中至少一個成立)。而且目標是最小化點權之和,恰好也是最小割的優化目標。
  • 建圖:新建源點\(s\)和匯點\(t\)。每個\(X\)部向\(Y\)部連的邊轉化為連容量為\(\infty\)的有向邊,\(s\)向每個\(X\)部的點\(u\)連容量為\(w_u\)的有向邊,\(Y\)部的每個點\(v\)向點\(t\)連容量為\(w_v\)的有向邊。最後答案就是最小割。

1.5.3最大點權獨立集

  • 分析:將點獨立集的條件\(\forall (u,v)\in E\),都有\(u\in V^{'}\)\(v\in V^{'}\)不同時成立改寫為布林表示式:\(\neg(u\in V^{'}\wedge v\in V^{'})\),化簡得\(u\in \overset{-}{V^{'}}\vee v\in \overset{-}{V^{'}}\),這個式子和點覆蓋集的條件有點像。其實覆蓋集和獨立集是對偶問題,所以答案可以通過覆蓋集求的。
  • 最終答案就是總點權減去最小點權覆蓋集的答案,即\(ans=\sum_{v\in V}w_v-\sum_{v\in V^{'}w_v}\)

2.最大流

2.1無源匯上下界可行流

  • 題目描述:給定有向圖\(G=(V,E)\),每條邊都有一個流量上界和流量下界。要求一個滿足流量限制的方案。

  • 建圖方式:記每條邊的上界為\(up\),下界為\(down\)

    • 先讓每條邊流滿下界的流量,即將每條邊的容量設定為\(up-down\),下界設為\(0\),但現在不滿足流量守恆
    • 建立源點\(s\)和匯點\(t\)
    • 記錄每個點\(u\)的流入流量\(in_u\)和流出流量\(out_u\),以流滿下界為標準
    • 如果\(in_u\ge out_u\),說明\(u\)要向外多輸出\(in_u-out_u\)的流量,從\(s\)\(u\)連容量為\(in_u-out_u\)的邊
    • 如果\(in_u\le out_u\),說明\(u\)要向外多輸入\(out_u-in_u\)的流量,從\(u\)\(t\)連容量為\(out_u-in_u\)的邊
    • 如果最後流量的最大值不等於從\(s\)中流出的滿流,說明不滿足流量守恆,因此無解
  • 輸入方案:對於原圖的每條邊,流量為反向邊的流量\(f\)加上正向變的容量下界\(down\)

  • 程式碼:

    struct edges
    {
        int v,nxt,f,down;
    }e[M];
    void add(int u,int v,int down,int up)
    {
        e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=up-down,e[cnt].down=down,head[u]=cnt++;
        e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,head[v]=cnt++;
    }
    int main()
    {
        S=0,T=n+1;
        for(int i=1;i<=m;i++)
        {
            int a,b,down,up;
            cin>>a>>b>>down>>up;
            add(a,b,down,up);
            out[a]+=down;
            in[b]+=down;   // A[a]-=c,A[b]+=c;
        }
        int tot=0;
        for(int u=1;u<=n;u++)
          if(in[u]-out[u]>0) add(S,u,0,in[u]-out[u]) ,tot+=in[u]-out[u];
          else if(out[u]-in[u]>0) add(i,T,0,out[u]-in[u]);
          //if(A[u]>0) add(S,i,0,A[u]) ,tot+=A[u];
          //else if(A[u]<0) add(i,T,0,-A[u]);
          
          if(dinic()!=tot) cout<<"NO"<<endl;
          else
          {
              cout<<"YES"<<endl;;
              for(int i=0;i<m*2;i+=2)
              cout<<e[i^1].f+e[i].down<<endl;
          }
    }
    

2.2有源匯上下界最大流

  • 題目描述:給定有向圖\(G=(V,E)\),每條邊都有一個流量上界和流量下界,給定源點\(s\)和匯點\(t\),求從\(S\)流到\(T\)的最大流。

  • 建圖方式:對於上下界的處理與無源匯上下界可行流相同,不同點在於對原圖中\(s\)\(t\)的處理

    • 建立虛擬源匯點\(S\)\(T\),對於上下界的限制建完邊後,需要在原圖中新建一條從匯點\(t\)到源點\(s\),容量為\(\infty\)的邊。然後以虛擬源匯點\(S\)\(T\)跑一邊最大流。
    • 如果跑出的最大流不等於從源點滿流的值,說明流量不守恆,無解
    • 記從\(t\)\(s\)連的容量為\(\infty\)的邊的流量為\(res_1\),然後斷開這條邊,把這條邊的正反邊容量設為\(0\),然後從原來的源匯點\(s\)\(t\)再跑一邊最大流記答案為\(res_2\),那麼最終的最大流為\(res_1+res_2\)
  • 程式碼:

    struct edges
    {
        int nxt,v,f;
    }e[M];
    void add(int u,int v,int f)
    {
        e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,head[u]=cnt++;
        e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,head[v]=cnt++;
    }
    int main()
    {
        S=0,T=n+1;
        cin>>n>>m>>s>>t;
        for(int i=1;i<=m;i++)
        {
            int a,b,c,d;
            cin>>a>>b>>c>>d;
            add(a,b,d-c);
            A[a]-=c,A[b]+=c;
        }
        int sum=0;
        for(int i=1;i<=n;i++)
        if(A[i]>0) add(S,i,A[i]),sum+=A[i];
        else if(A[i]<0) add(i,T,-A[i]);
        
        add(t,s,INF);
        
        if(dinic()!=sum) cout<<"No Solution"<<endl;
        else 
        {
            int res=e[cnt-1].f;
            S=s,T=t;
            e[cnt-1].f=e[cnt-2].f=0;
            cout<<res+dinic()<<endl;
        }
    }
    

2.3有源匯上下界最小流

  • 題目描述:給定有向圖\(G=(V,E)\),每條邊都有一個流量上界和流量下界,給定源點\(s\)和匯點\(t\),求從\(S\)流到\(T\)的最小流。

  • 建圖方式:和有源匯上下界最大流幾乎完全一樣,不同點在於第二遍跑最大流時,利用\(S->T\)最大等價於\(T->S\)最小的轉換,從\(t\)\(s\)跑第二遍最大流,答案記為\(res_2\),那麼最終答案就是\(res_1-res_2\)

  • 程式碼:

    //最後一部分不同
    int res=e[cnt-1].f;
    e[cnt-1].f=e[cnt-2].f=0;
    S=t,T=s;//S->T最小,等價於T->S最大
    cout<<res-dinic()<<endl;
    

2.4多源匯最大流

  • 題目描述:給定一個包含\(n\) 個點 \(m\) 條邊的有向圖,並給定每條邊的容量,邊的容量非負。其中有 \(S_c\) 個源點,\(T_c\) 個匯點。圖中可能存在重邊和自環。求整個網路的最大流。
  • 建圖方法:
    • 建立超級源匯點\(S\)\(T\)
    • \(S\)向原圖中的每個源點\(s_i\)連容量為\(\infty\)的邊,原圖中的每個匯點\(t_i\)\(T\)連容量為\(\infty\)的邊,其他邊正常連。
    • 最後跑一邊最大流即可。

3.費用流

3.1常見模型

  • 二分圖最優匹配
  • 網格圖模型
  • 最大權不相交路徑

4.常用技巧

4.1拆點(如果對點有限制)

  • 拆點就是將一個點拆成入點和出點兩個點,並在兩個點之間建一條邊。
  • 拆點是為了實現對點的限制。

4.2常見模型

  • 二維網格模型:對行和列有限制,建立二分圖,\(X\)部是行,\(Y\)部是列,用連線所在行列的邊表示對應格子的決策
  • 流量平衡是費用流的模型:利用每個點流入流量等於流出流量來滿足等式。
  • 一維模型:一般的建模方式是直接建一條鏈:\(S\rightarrow x_1\rightarrow x_2\rightarrow...\rightarrow T\),通過容量限制和流量平衡滿足條件,利用費用流求答案
  • 對於有依賴、捆綁、偏序關係的問題,考慮轉化為最大權閉合子圖,利用最小割求解

5.模板

5.1Dinic

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N,M,INF;
int n,m,S,T,cnt;
int head[N],d[N],cur[N];
struct edges
{
	int v,nxt,f;
}e[M*2];
void add(int u,int v,int f)
{
	e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,head[u]=cnt++;
	e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,head[v]=cnt++;
}

bool bfs()
{
	memset(d,-1,sizeof d);
	queue<int>q;
	q.push(S);
	d[S]=0,cur[S]=head[S];
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=e[i].nxt)
		{
			int v=e[i].v,f=e[i].f;
			if(d[v]==-1&&f)
			{
				d[v]=d[u]+1;
				cur[v]=head[v];
				if(v==T) return true;
				q.push(v);
			}
		}
	}
	return false;
}
int find(int u,int limit)
{
	if(u==T) return limit;
	int flow=0;
	for(int i=cur[u];~i&&flow<limit;i=e[i].nxt)
	{
		cur[u]=i;
		int v=e[i].v,f=e[i].f;
		if(d[v]==d[u]+1&&f)
		{
			int t=find(v,min(f,limit-flow));
			if(!t) d[v]=-1;
			e[i].f-=t,e[i^1].f+=t,flow+=t;
		}
	}
	return flow;
}
int dinic()
{
	int ans=0,flow;
	while(bfs()) while(flow=find(S,INF)) ans+=flow;
	return ans;
}

int main()
{
    memset(head,-1,sizeof head);
    cin>>n>>m>>S>>T;
	for(int i=1;i<=m;i++)
	{
		int u,v,f;
		cin>>u>>v>>f;
		add(u,v,f);
    }
	cout<<dinic()<<endl;
	return 0;	
}

5.2 費用流

//最小費用最大流,將EK演算法的BFS換成SPFA,若存在負環,該模板不適用,要加入消圈發法 



#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N=5005,M=50005*2,inf=1e8;
int n,m,S,T,cnt;
int head[N],d[N],st[N],pre[N],incf[N];
struct edges
{
    int v,nxt,f,c;
}e[M];
void add(int u,int v,int f,int c)
{
     e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,e[cnt].c=c,head[u]=cnt++;
     e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,e[cnt].c=-c,head[v]=cnt++;
}
bool spfa()
{
    memset(d,0x7f,sizeof d);
    memset(incf,0,sizeof incf);
    queue<int>q;
    q.push(S);
    d[S]=0,incf[S]=inf;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=0;
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].v,f=e[i].f,c=e[i].c;
            if(f&&d[v]>d[u]+c)
            {
                d[v]=d[u]+c;
                pre[v]=i;
                incf[v]=min(incf[u],f);
                if(!st[v])
                {
                    st[v]=1;
                    q.push(v);
                }
                
            }
        }
    }
    return incf[T]>0;
}
void EK(int &flow,int &cost)
{
    flow=cost=0;
    while(spfa())
    {
        int t=incf[T];
        flow+=t,cost+=t*d[T];
        for(int i=T;i!=S;i=e[pre[i]^1].v)
        {
            e[pre[i]].f-=t,e[pre[i]^1].f+=t;
        }
    }
}
int main()
{
    memset(head,-1,sizeof head);
    cin>>n>>m>>S>>T;
    for(int i=1;i<=m;i++)
    {
        int u,v,f,c;
        cin>>u>>v>>f>>c;
        add(u,v,f,c);
    }
    int flow,cost;
    EK(flow,cost);
    cout<<flow<<" "<<cost<<endl;
    return 0;
}

參考連結

網路流建模方式總結

國家集訓隊胡泊濤最小割應用