網絡流及建模專題(上)

前言

不斷更新中……
這幾天新坑填不下去了,回來回顧一些經典的模型套路,先拿網絡流開刀,竊以爲洛谷這幾道網絡流的題目還是非常具有代表性的,涵蓋了網絡流調整多解計數最小割最大權閉合子圖問題。
還涵蓋了圖論(二分圖)中的一些結論和:最小不相交路徑覆蓋、最小可相交路徑覆蓋、二分圖最大點權獨立集、二分圖最小點權覆蓋集等問題,這裏將簡略介紹一下。

本專題包含六道題:P2765、P2764、P2763、P2766、P2774、P2805

P2765 網絡流調整技巧
P2763 二分圖多重匹配問題
P2764 最小不相交路徑覆蓋、最小可相交路徑覆蓋
P2766 一類dp計數問題、最多不相交路徑
P2774 二分圖最大點權獨立集、二分圖最小點權覆蓋集
P2805 最大權閉合子圖

前提

  • 知道網絡流的基本用法。
  • 知道最大流最小割定理。
  • 知道二分圖的定義。
  • 有一個穩定好用的Dinic求網絡流的板子。

P2765 魔術球問題

前言

這道題應該可以通過貪心的方法過,不過既然放在了網絡流專題裏面,我們就認真的用網絡流試一試並總結一下經驗。

題解

添加一個小球相當於在網絡中出現了一個從源點SS到匯點TT的流量。

分析一下題目:同一個柱子的小球要求可以與前一個小球形成和爲完全平方數的情況,當一個新的小球到來的時候,可以加入其中的一根柱子,當然也可以另外開啓一個新的柱子,使用的柱子的總數不得超過nn

我們另開一個節點TT'用來表示柱子的上限,連接一條容量爲nn的邊(T,T)(T',T)

我們嘗試把小球拆成兩個點BBBB',並且由SSBB連接一條容量爲11的邊。由BB'TT連接一條容量爲11的邊,爲什麼容量爲11?這限制了每個球后面都只能緊鄰地放一個球。

如果數大的小球與某個數小的小球之間組合滿足和爲完全平方數(即大球可以放在小球后面),我們就從連接一條容量爲11的邊(B,B)(B_{大球},B_{小球}')。這代表該大球可以放在小球后面不需要開啓新的柱子。

每個球都可以連接一條容量爲11的邊(B,T)(B,T')。代表該小球獨立開啓了一個新的柱子。

如下圖所示:
這裏寫圖片描述
回顧一下這張圖:
BB流向TT'的流量代表BB球開啓了一個新的柱子,而從BB_{大球}BB_{小球}'的流量代表BB_{大球}放在了BB_{小球}的後面。

這只是建圖模型,我們在解決這道題的時候不能跑一遍網絡流完事,而是需要使用迭代加深搜索的思想,增量式建圖,每次加入一個球,然後在殘餘網絡中跑最大流,判斷能否跑滿,如果跑不滿,那麼答案就是上次跑滿時候的結果。

最後,這道題要求輸出方案,我們只需要在殘餘網絡裏面遍歷去邊的容量,然後就可以得到方案了,這裏不必細說,看我的代碼就可以了。

參考代碼 見附錄

放置、匹配的問題,都可以往網絡流方面思考。


P2764 最小路徑覆蓋

什麼是最小路徑覆蓋?

最小路徑覆蓋分爲兩種

  • 最小不相交路徑覆蓋
  • 最小可相交路徑覆蓋

最小不相交路徑覆蓋的做法

構造一個新圖,將一個點BB拆成B1B_1B2B_2兩個點,在原圖中如果有邊(A,B)(A,B)那麼在新圖中連接邊(A1,B2)(A_1,B_2)
然後形成了一張二分圖,結論就是最小不相交路徑覆蓋=原圖節點數-二分圖的最大匹配

如何理解這個過程呢?

一開始每個點都是一條路徑。
在二分圖中,每形成一個匹配,就相當於一條路徑將匹配兩邊的點給合併了,也就是合併了兩個路徑,這樣一直匹配下去,路徑數就在不斷的減少,所以最終形成的就是最小路徑覆蓋,最後的路徑數就是一開始原圖的節點數-匹配數。

最小可相交路徑覆蓋的做法

這個問題與本題沒有關係,只是擴展,可以暫時跳過。
解這個小問的方法是把這個問題化歸到最小不相交路徑覆蓋上去。

可相交也就是說如果兩點可達,那麼不管這兩點中間有多少節點,我都可以去經過。

先跑出可達矩陣,用Floyd算法,算出兩兩點之間的可達性。
然後如果兩兩可達,則建邊。建成新圖以後,就變成了最小不相交路徑覆蓋問題了。

理解

但是如果兩個點a和b是連通的,只不過中間需要經過其它的點,那麼可以在這兩個點之間加邊,那麼a就可以直達b,不必經過中點的,那麼就轉化成了最小不相交路徑覆蓋。

這道題同樣需要輸出方案,在殘餘網絡中遍歷邊處理就可以了。

參考代碼 見附錄


P2763 試題庫問題

我覺得這是一個非常典型的二分圖問題。

kk種類型是二分圖左側的kk個點。

nn個試題是二分圖右側的nn個點。

如果某試題屬於某種類型,就在試題和類型之間連接一條邊,容量爲11

要求選出numnumtt類型的題目,那麼就在源點SStt類型點之間加入一條容量爲numnum的邊。

而每道題目只能使用一次,因此在題目pp和匯點TT之間連接一條容量爲11的邊。

然後跑一遍最大流就可以得到答案了。
這裏寫圖片描述

參考代碼見附錄


P2766 最長不下降子序列問題

這類問題屬於動態規劃方程已知,對解方案計數的問題。
還有的是要求最短路的數量的問題,我之前有在我的博客裏寫過,感興趣的各位可以找找看。

第一問

要求求出最長不下降子序列長度,這個使用動態規劃很容易解決,但是我們必須要在這一步中處理出以a[i]a[i]元素爲結尾的最長非降子序列的長度數組f[i]f[i]

第二問

要求在所有的點都不能重複使用的時候,能找到多少個最長非減子序列。

在網絡流處理問題的模型中,如果一個點不能重複利用,那麼我們可以將這個點PP拆成兩個點PsP_sPtP_t,並由PsP_sPtP_t連接一條容量爲11的邊。所有流入這個點的邊都與PsP_s相連,所有流出這個點的邊都與PtP_t相連。

在這個題中,我們從f[i]f[i]數組出發,建立一個DAG(有向無環圖)。即滿足f[j]+1=f[i]f[j]+1=f[i]a[j]a[i]a[j] ≤ a[i](j,i)(j,i)我們都建立一條iijj的容量爲infinf的邊。

然後建立一個超級源點SS,和超級匯點TTSSf[i]==mxlenf[i] == mxlen的點ii連邊,TTf[i]==1f[i] == 1的點ii連邊,容量均爲11
然後按照我上面說的方法拆點。
舉例:
3,6,2,53,6,2 ,5
這個序列的mxlen=2mxlen=2,f[]=1,2,1,2f[]={1,2,1,2},我們建成的圖就是:
這裏寫圖片描述
這樣的話,跑一遍最大流就知道有多少條長度爲mxlenmxlen的不相交的路徑了。

第三問

若某個點可以無限用的話,那麼這個點就不應該被拆。

對第二問的圖稍做修改,如下
這裏寫圖片描述

這裏有個小細節xnx_nx1x_1直接相連接的時候,他們之間的邊權不能設爲 infinf而應該是11

另一個小細節就是如果mxlen==1mxlen==1那麼直接輸出nn不需要建圖。

參考代碼 見附錄

P2774 方格取數問題

由這個問題引出的模型是二分圖最大點權獨立集

二分圖點權最大獨立集:帶點權二分圖G中的一個子集V,其中一條邊的兩個端點不能同時屬於V,且V中點權和最大。

二分圖最大點權獨立集對偶問題的是二分圖的最小點權覆蓋集,即用最小點權和的點集去覆蓋所有的邊,解法就是按照如下的方法建圖跑最小割。

相鄰的格子數不能同時取到,他們兩個是互斥的,我們對格子進行黑白染色以後,形成了一張二分圖,這個問題轉化爲求這張二分圖的最大點權獨立集

這種模型可以採用求最小割的方法來做

SS點向白點連邊,容量爲白點的權值,從黑點向TT點連邊,容量爲黑點的權值。
白點和黑點之間如果有互斥關係,那麼從白點向黑點連接一條容量爲infinf的邊,形成一張網絡圖。

我們觀察這張圖的最小割,我們發現容量爲infinf的邊一定不能被割掉,那麼這條邊兩端的點u,vu,v就必然有(S,u)(S,u)被割掉或者是(v,T)(v,T)被割掉,被割掉的含義就是這個點的權值我不要了。由於我們得到的是最小割,也就是說我們剩下的點的權值之和一定是最大的,且不互斥的,因此用總的權值和減去最小個的值就是最大權獨立集的權值和,也就是本題的答案。

例如下圖的答案就是
這裏寫圖片描述
ans=(a+b+c+d+e+f)(a+b+d+e)=c+fans = (a+b+c+d+e+f) - (a+b+d+e) = c+f

##參考代碼 見附錄


P2805 植物大戰殭屍

這個問題要引出的模型就更厲害啦!
那就是:
最大權閉合子圖
最大權閉合子圖
最大權閉合子圖
重要的問題說三遍,這個模型很重要,能解決非常多有趣的問題,主要被用來解決在有依賴性條件下的規劃問題。

什麼是最大權閉合子圖?

閉合子圖的定義是:在子圖中,所有點的出邊所指向的節點仍在該子圖內。

這裏寫圖片描述

在這張圖中,閉合子圖有:
{E}{D,E}{F,E}{A,D,E}{B,D,E}{F,E}{C,F,E}\{E\}、\{D,E\}、\{F,E\}、\{A,D,E\}、\{B,D,E\}、\{F,E\}、\{C,F,E\}

最大權閉合子圖就是找一個最大的權值和的閉合子圖。

如何找最大權閉合子圖?

我們把這個圖進行變形,變形成可以用最小割解決的問題,然後用最小割解決它。
變形方法:**如果一個點的權值爲正,那麼從SS點向該點連接一條容量爲權值的邊,如果這個點的權值爲負,那麼從該點向TT點連接一條容量爲權值絕對值的邊,原圖中的所有邊容量均設置爲infinf。**然後跑一遍最小割,原圖中正的點權和減去最小割得到的就是最大權閉合子圖。

證明這裏就不證明了,有興趣的同學可以去網上搜搜看。

如何解決依賴關係下的選取問題?

如果要選取物品AA必須要先選取物品BB,則說物品AA依賴於物品BB,反映在圖中就是AABB連有一條邊。

選取某個物品可能獲利也有可能要付出代價,如果獲利則說該物品的權值符號爲正,如果獲取該物品要付出代價,則說該物品的權值符號爲負。

而一種選取方案則必然屬於原圖中的一個閉合子圖,如果不閉合這個選取方案必然非法。

因此,求一種獲利最大的選取方案就相當於在求一個最大權閉合子圖。

這道題怎麼做?

這道題中,殭屍只能從每一行的最右端開始吃,想吃(x,y)(x,y)位置的植物必須先吃掉(x,y+1)(x,y+1)位置的植物,這樣的話,就形成了一個依賴關係(x,y)(x,y)依賴於(x,y+1)(x,y+1),寫作(x,y)>(x,y+1)(x,y)->(x,y+1)
本題中還有另外的一個依賴關係,那就是某植物AA可以保護其他一些植物BB,也就是存在依賴關係:(xB,yB)>(xA,yA)(x_B,y_B)->(x_A,y_A)
根據這些依賴關係,以及吃掉植物的獲利,我們可以建立一張圖。

然後在這張圖求一個最大權閉合子圖,就是我們的答案。

但是我們忽略了一點,就是說可能存在無敵植物,這些植物的依賴關係形成了一個環,這樣就不能直接跑最大權閉合子圖了,所以,我們第一步要先去掉這張依賴關係圖中的。使用什麼來去環呢?答案是拓撲排序。

先觀察可能出現環的情形:
這裏寫圖片描述

這種情形下,環應該被去掉,因爲環代表無敵關係。而依賴於環的點JJ也應該被去掉,因爲環被去掉了以後這個依賴關係永遠不能成立。

如何去掉環和依賴於環的點?

其實很簡單,我們將圖中所有的邊反向,使用拓撲排序,將所有沒有被訪問的節點去掉。
這一步的核心操作就在反向

去掉點之後,直接跑最大權閉合子圖就可以了。

參考代碼 見附錄


附錄

Dinic網絡流模板

以下代碼均不含模板

const int inf = 1e9;
const int maxm = 800001;
const int maxn = 10001;
int node,src,dest,edge;
int ver[maxm],flow[maxm],nxt[maxm];
int head[maxn],work[maxn],dis[maxn],q[maxn];
void prepare(int _node,int _src,int _dest)
{
    node=_node,src=_src,dest=_dest;
    for(int i=0; i<node; ++i)head[i]=-1;
    edge=0;
}
void addedge(int u,int v,int c)
{
    ver[edge]=v,flow[edge]=c,nxt[edge]=head[u],head[u]=edge++;
    ver[edge]=u,flow[edge]=0,nxt[edge]=head[v],head[v]=edge++;
}
bool Dinic_bfs()
{
    int i,u,v,l,r=0;
    for(i=0; i<node; ++i)dis[i]=-1;
    dis[q[r++]=src]=0;
    for(l=0; l<r; ++l)
        for(i=head[u=q[l]]; i>=0; i=nxt[i])
            if(flow[i]&&dis[v=ver[i]]<0)
            {
                dis[q[r++]=v]=dis[u]+1;
                if(v==dest)return 1;
            }
    return 0;
}
int Dinic_dfs(int u,int exp)
{
    if(u==dest)return exp;
    for(int &i=work[u],v,tmp; i>=0; i=nxt[i])
        if(flow[i]&&dis[v=ver[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0)
        {
            flow[i]-=tmp;
            flow[i^1]+=tmp;
            return tmp;
        }
    return 0;
}
int Dinic_flow()
{
    int i,ret=0,delta;
    while(Dinic_bfs())
    {
        for(i=0; i<node; ++i)work[i]=head[i];
        while(delta=Dinic_dfs(src,inf))ret+=delta;
    }
    return ret;
}

P2765

// 網絡流模板省去
int n,m = 2000;
int main(){
    scanf("%d",&n);
    prepare(2*m+3,0,2*m+2);
    addedge(2*m+1,2*m+2,n);
    for(int i = 1;;++i){
        addedge(0,2*i-1,1);
        addedge(2*i-1,2*m+1,1,1);
        addedge(2*i,2*m+2,1);
        for(int j = 1;j < i;++j){
            int rt = sqrt(i+j+0.5);
            if(rt*rt == i+j) addedge(2*i-1,2*j,1,2);
        }
        int ans = Dinic_flow();
        if(ans < i){
            printf("%d\n",i-1);
            int vc = 0;
            //生成方案
            vector<int> G[n];
            vector<int> bl(i,-1);
            for(int e = 0;e < edge;e += 2){
                if(flow[e] != 0) continue;
                if(tps[e] == 1){
                    int u = (ver[e+1]+1)/2;
                    G[vc].push_back(u);
                    bl[u] = vc;
                    vc++;
                }
                else if(tps[e] == 2){
                    int u = (ver[e+1]+1)/2;
                    int v = (ver[e]+1)/2;
                    bl[u] = bl[v];
                    G[bl[u]].push_back(u);
                }
            }
            for(int i = 0;i < n;++i){
                for(auto v : G[i]) printf("%d ",v);
                puts("");
            }
            break;
        }
    }
    return 0;
}

P2764

int pnxt[maxn];
int deg[maxn];
int n,m;
int main(){
    cin>>n>>m;
    prepare(2*n+2,0,2*n+1);
    for(int i = 0;i < m;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,n+v,1);
    }
    for(int i = 1;i <= n;++i) addedge(0,i,1),addedge(i+n,2*n+1,1);
    int ans = Dinic_flow();
    for(int e = 0;e < 2*m;e += 2){
        if(flow[e] == 0){
            int u = ver[e+1];
            int v = ver[e] - n;
            pnxt[u] = v;
            deg[v]++;
        }
    }
    for(int i = 1;i <= n;++i){
        if(deg[i]) continue;
        int v = i;
        while(v){
            printf("%d ",v);
            v = pnxt[v];
        }
        puts("");
    }
    cout<<n-ans<<endl;
    return 0;
}

P2763

int n,k;
int kn[30];
int main(){
    cin>>k>>n;
    prepare(1+k+n+1,0,1+k+n);
    for(int i = 1;i <= k;++i) cin>>kn[i];
    for(int i = 1;i <= k;++i) addedge(0,i,kn[i]);
    for(int i = 1;i <= n;++i){
        int num;scanf("%d",&num);
        for(int j = 1;j <= num;++j){
            int tt;scanf("%d",&tt);
            addedge(tt,i+k,1,1);
        }
    }
    for(int i = 1;i <= n;++i) addedge(i+k,1+k+n,1);
    int ans = Dinic_flow();
    vector<int> G[30];
    for(int e = 0;e < edge;++e){
        if(flow[e] == 0 && tps[e]){
            int u = ver[e+1];
            int v = ver[e]-k;
            G[u].push_back(v);
        }
    }
    for(int i = 1;i <= k;++i){
        printf("%d: ",i);
        for(auto v : G[i]) printf("%d ",v);
        puts("");
    }

    return 0;
}

P2766

int n,dp[507],f[507],a[507];
int main(){
    memset(dp,0x3f,sizeof(dp));
    cin>>n;
    for(int i = 1;i <= n;++i){
        int tmp;
        cin>>tmp;
        a[i] = tmp;
        int loc = upper_bound(dp+1,dp+1+n,tmp) - dp;
        f[i] = loc;
        dp[loc] = tmp;
    }
    int s = lower_bound(dp+1,dp+1+n,0x3f3f3f3f)-dp-1;
    cout<<s<<endl;
    prepare(2*n+2,0,2*n+1);
    for(int i = n;i >= 1;--i){
        addedge(i,i+n,1);
        if(f[i] == s) addedge(0,i,505);
        if(f[i] == 1) addedge(i+n,2*n+1,505);
        for(int j = i-1;j >= 1;--j){
            if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);
        }
    }
    cout<<Dinic_flow()<<endl;
    if(s == 1) {
        return 0*printf("%d\n",n);
    }
    prepare(2*n+2,0,2*n+1);
    for(int i = n;i >= 1;--i){
        addedge(i,i+n,1);
        if(f[i] == s){
            if(i == n) addedge(0,i+n,505);
            else addedge(0,i,505);
        }    
        if(f[i] == 1){
            if(i == 1) addedge(i,2*n+1,505);
            else addedge(i+n,2*n+1,505);
        }
        for(int j = i-1;j >= 1;--j){
            if(j == 1 && i == n) continue;
            if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);
        }
    }
    int ans = Dinic_flow();
    if(f[1] + 1 == f[n]) ans ++;
    cout<<ans<<endl;
}

P2774

int n,m;
int a[101][101];
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
int main(){
    long long sum = 0;
    cin>>m>>n;
    for(int i = 1;i <= m;++i){
        for(int j = 1;j <= n;++j) {
            scanf("%d",&a[i][j]);
            sum += a[i][j];
        }
    }

    prepare(n*m+2,0,n*m+1);
    
    for(int i = 1;i <= m;++i){
        for(int j = 1;j <= n;++j){
            int u = (i-1)*n+j;
            if((i+j)%2 == 0) addedge(0,u,a[i][j]);
            else addedge(u,n*m+1,a[i][j]);
            
            if((i+j)%2 == 0)
            for(int t = 0;t < 4;++t){
                int nx = i + dx[t],ny = j + dy[t];
                if(nx < 1 || nx > m || ny < 1 || ny > n) continue;
                int v = (nx-1)*n+ny;
                addedge(u,v,inf);
            }
        }
    }
    
    cout<<sum - Dinic_flow()<<endl;
    return 0;
}

P2805

typedef pair<int,int> pii;
int a[100][100];
vector<pii> ps[100][100];
int n,m;
long long sum;
#define pr(x) cout<<#x<<":"<<x<<endl
int main(){
    //freopen("ts3.txt","r",stdin);
    cin>>n>>m;
    prepare(n*m+2,0,n*m+1);
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= m;++j){
            int num;
            scanf("%d%d",&a[i][j],&num);
            for(int t = 0;t < num;++t){
                int x,y;scanf("%d%d",&x,&y);
                x++,y++;
                ps[i][j].push_back(make_pair(x,y));
            }
            if(j != 1) ps[i][j].push_back(make_pair(i,j-1));
        }
    }
    void toposort();
    toposort();
    long long ans = (long long)Dinic_flow();
    cout<<sum-ans<<endl;
}
int deg[30][31],vis[31][31],f[31][31];
void toposort(){
    queue<pii> Q;
    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        for(auto p : ps[ux][uy]) { deg[p.first][p.second] ++;}
    }
    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        if(!deg[ux][uy]) Q.push(make_pair(ux,uy));
    }

    while(!Q.empty()){
        pii p = Q.front();Q.pop();
        sum += max(0,a[p.first][p.second]); 
        vis[p.first][p.second] = 1;
        for(auto p2:ps[p.first][p.second]){
            deg[p2.first][p2.second]--;
            if(deg[p2.first][p2.second]==0) Q.push(p2);
        }
    }

    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        if(!vis[ux][uy]) continue;
        for(auto p : ps[ux][uy]) {
            if(vis[p.first][p.second]) addedge((p.first-1)*m+p.second,(ux-1)*m+uy,inf);
        }
        if(a[ux][uy] >= 0) addedge(0,(ux-1)*m+uy,a[ux][uy]);
        else addedge((ux-1)*m+uy,n*m+1,-a[ux][uy]);
    }
    
}

原創:西安交大 蔡少斐

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