网络流问题的常见套路

来源:互联网 发布:湖北大学知行学院宿舍 编辑:程序博客网 时间:2024/05/17 19:23

本文在接下来的一段时间内可能会经常扩充或修改,如有谬误敬请谅解。[2017.11.24]

“拆点”

拆点,顾名思义,就是把一个点拆成两个点。因为在网络流的模型中,割特指边集而非点集,所以想要实现“可以被割掉的点”,就用用到拆点思想。另外,这种方法也可以被理解为:使点同边一样有一个容量上限。

把点A分成两个,一个叫A1,表示A的“入点”;另一个叫A2,表示A的“出点”。A的所有出边都从A2连出,所有入边都连向A1。再连一条边从A1指向A2,其边权为点A的“点容量”。

例题:[USACO5.4]奶牛的电信(copy from 洛谷,特此表示感谢)

P1345 [USACO5.4]奶牛的电信Telecowmunication

题目描述

农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流。这些机器用如下的方式发送电邮:如果存在一个由c台电脑组成的序列a1,a2,…,a(c),且a1与a2相连,a2与a3相连,等等,那么电脑a1和a(c)就可以互发电邮。

很不幸,有时候奶牛会不小心踩到电脑上,农夫约翰的车也可能碾过电脑,这台倒霉的电脑就会坏掉。这意味着这台电脑不能再发送电邮了,于是与这台电脑相关的连接也就不可用了。

有两头奶牛就想:如果我们两个不能互发电邮,至少需要坏掉多少台电脑呢?请编写一个程序为她们计算这个最小值。

以如下网络为例:

1*

/ 3 - 2*

这张图画的是有2条连接的3台电脑。我们想要在电脑1和2之间传送信息。电脑1与3、2与3直接连通。如果电脑3坏了,电脑1与2便不能互发信息了。

输入输出格式

输入格式: 第一行
四个由空格分隔的整数:N,M,c1,c2.N是电脑总数(1<=N<=100),电脑由1到N编号。M是电脑之间连接的总数(1<=M<=600)。最后的两个整数c1和c2是上述两头奶牛使用的电脑编号。连接没有重复且均为双向的(即如果c1与c2相连,那么c2与c1也相连)。两台电脑之间至多有一条连接。电脑c1和c2不会直接相连。

第2到M+1行 接下来的M行中,每行包含两台直接相连的电脑的编号。

输出格式: 一个整数表示使电脑c1和c2不能互相通信需要坏掉的电脑数目的最小值。

输入输出样例

样例输入:

3 2 1 2
1 3
2 3

样例输出:

1

附上代码

#include<cstdio>#include<cstdlib>#include<algorithm>#include<vector>#include<queue>#include<cstring>using namespace std;const int maxn=605,INF=0x3f3f3f3f;struct Dinic{    int n,s,t;    struct Edge{        int from,to,cap,flow;    };    vector<int>G[maxn];    vector<Edge>edges;    int cur[maxn],d[maxn];    bool vis[maxn];    void addedge(int f,int t,int c){        edges.push_back((Edge){f,t,c,0});        edges.push_back((Edge){t,f,0,0});        int m=edges.size();        G[f].push_back(m-2);        G[t].push_back(m-1);    }    bool BFS(){        //memset(d,0,sizeof(d));        memset(vis,0,sizeof(vis));        queue<int>Q;        Q.push(s);d[s]=0;vis[s]=1;        while(!Q.empty()){            int x=Q.front();Q.pop();            for(int i=0;i<G[x].size();i++){                Edge& e=edges[G[x][i]];                if(!vis[e.to] && e.cap>e.flow){                    vis[e.to]=1;                    d[e.to]=d[x]+1;                    Q.push(e.to);                }            }        }        return vis[t];    }    int DFS(int x,int a){        if(x==t || a==0)return a;        int flow=0,f;        for(int& i=cur[x];i<G[x].size();i++){            Edge& e=edges[G[x][i]];            if(e.cap>e.flow && d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow)))>0){                e.flow+=f;                edges[G[x][i]^1].flow-=f;                flow+=f;                a-=f;                if(a==0)break;            }        }        return flow;    }    int MaxFlow(int S,int T){        s=S;t=T;int flow=0;        while(BFS()){            memset(cur,0,sizeof(cur));            flow+=DFS(S,INF);        }        return flow;    }}dinic;int main(){    int N,M,c1,c2;scanf("%d%d%d%d",&N,&M,&c1,&c2);    for(int i=1;i<=N;i++){        dinic.addedge(i,i+N,1);    }    for(int i=1;i<=M;i++){        int x,y;scanf("%d%d",&x,&y);        dinic.addedge(x+N,y,INF);        dinic.addedge(y+N,x,INF);    }    int mf=dinic.MaxFlow(c1+N,c2);    printf("%d\n",mf);    return 0;}

本题求至少要删除多少个点使C1、C2不连通,问题的实质是一个最小割,跑一个最大流即可,因为只允许割点,不允许割边,所以原有的边边权都为INF。点权为1,表示每个点最多只能被删除一次。