首页 > 代码库 > 网络流

网络流

1. 最大流问题。

实现t点流量最大。这是网络流的基本问题。通常三种方法,EK,Dinic,ISAP。EK最好拍,但是每次找增广路都要BFS一次实在伤不 起,Dinic加上各种神神优化才赶得上没优化的ISAP,弃之。ISAP难拍难理解,但是花点时间理解一下就好了,主要是当前弧优化和GAP优化比较难 理解,以及ISAP的坑爹的反向BFS。

模板如下,采用LRJ的边表,我觉得其实挺好用的,相比于链式前向星。
HDU 1532(网络流各类拍法测试裸题)
 
#include "cstdio"#include "vector"#include "cstring"#include "queue"using namespace std;#define maxn 405#define inf 100000000struct Edge{    int from,to,cap,flow;    Edge(int FROM,int TO,int CAP,int FLOW):from(FROM),to(TO),cap(CAP),flow(FLOW) {}};int d[maxn],p[maxn],gap[maxn],cur[maxn];bool vis[maxn];vector<int> G[maxn];vector<Edge> edges;int n,m;void addedge(int from,int to,int cap){    edges.push_back(Edge(from,to,cap,0));    edges.push_back(Edge(to,from,0,0));    int m=edges.size();    G[from].push_back(m-2);    G[to].push_back(m-1);}void bfs(int s,int t){    memset(vis,false,sizeof(vis));    memset(d,0,sizeof(d));    memset(p,0,sizeof(p));    d[t]=0;vis[t]=true;    queue<int> Q;Q.push(t);    while(!Q.empty())    {        int u=Q.front();Q.pop();        for(int v=0;v<G[u].size();v++)        {            Edge e=edges[G[u][v]^1];            if(!vis[e.from]&&e.cap>e.flow)            {                vis[e.from]=true;                d[e.from]=d[u]+1;                Q.push(e.from);            }        }    }}int augment(int s,int t){    int x=t,a=inf;    while(x!=s)    {        Edge e=edges[p[x]];        a=min(a,e.cap-e.flow);        x=e.from;    }    x=t;    while(x!=s)    {        edges[p[x]].flow+=a;        edges[p[x]^1].flow-=a;        x=edges[p[x]].from;    }    return a;}int maxflow(int s,int t){    int flow=0,u=s;    bfs(s,t);    memset(gap,0,sizeof(gap));    memset(cur,0,sizeof(cur));    for(int i=0;i<n;i++) gap[d[i]]++;    while(d[s]<n)    {        if(u==t)        {            flow+=augment(s,t);            u=s;        }        bool flag=false;        for(int v=cur[u];v<G[u].size();v++) //Advance        {            Edge e=edges[G[u][v]];            if(e.cap>e.flow&&d[u]==d[e.to]+1)            {                flag=true;                p[e.to]=G[u][v];                cur[u]=v;                u=e.to;                break;            }        }        if(!flag) //Retreat        {            int m=n-1;            for(int v=0;v<G[u].size();v++)            {                Edge e=edges[G[u][v]];                if(e.cap>e.flow) m=min(m,d[e.to]);            }            if(--gap[d[u]]==0) break;            gap[d[u]=m+1]++;            cur[u]=0;            if(u!=s) u=edges[p[u]].from;        }    }    return flow;}int main(){    //freopen("in.txt","r",stdin);    int u,v,w;    while(~scanf("%d%d",&m,&n))    {        for(int i=1;i<=m;i++)        {            scanf("%d%d%d",&u,&v,&w);            addedge(u,v,w);        }        printf("%d\n",maxflow(1,n));        for(int i=0;i<maxn;i++) G[i].clear();        edges.clear();    }}
View Code

2. 最小割问题

你会在各种资料上见到maxflow<=mincut,最小割到底是个啥玩意呢。我们先来说说割吧,你可以把割理解成在图中所有引导至t点的直接弧或间接弧的总和,说白了就是对最终流量产生影响的有用弧(你要知道图中很多点是到不了t点的,这些点所连的弧是没用的,废弃的)。割的容量即所有有用弧的总容量,就是最大割啦。有了最大割就不难理解最小割了,最小割就是在割上走的流量最少呗,但是这最少可不能是0, 我们定义最小值就是在最大流前提下的最小走过流量,所以最大流=最小割。

最小割是个很蹩脚的定义,但是确能解决一类特殊的问题,这类问题统称最小割问题。

如vijos 1590, 初看上去像个费用流,其实不是。洪水堵截,在不到t点的情况下,你可以在途中任一点进行堵截,我们当然选择在费用最小的点把它堵上。等等,这不就是最大流问题么,想一想最大流的增广路,增广路的最大流量由路上最小的容量决定,我们不妨把费用设为容量,那么在费用最小点堵截不就相当于找条增广路么。这就是最小割建模,最小最小,不是指结果最小,而是过程最小,最大流和最小割是不矛盾滴。当然,这题的难度不止于此,费用在点上而不在弧上,所以我们要拆点,这叫拆点网络流。拆点的方法:开双倍点,本点连影子点(容量为费用,注意这题源汇无费用,源汇与影子点的容量为inf),对于u-v有连接的,u连v‘(容量为inf),v连u’(无向图,容量为inf)。然后,就是这题的判定条件,如果maxflow=0就是不存在割喽。maxflow>=inf,无法堵截。剩余就是能堵截了。最后,这题数据卡EK,EK只能过80%的点,所以还是乖乖拍ISAP吧。

vijos 1524, 和上题类似,还是堵截类问题。不过好在不用拆点了,但是却是多汇点(你可以认为犯人可以从汇点逃跑),所以对于所有汇点,向超级汇点连一条边即可(容量inf)。这题不卡EK。

hdu 4289, 又是堵截类问题,orz,貌似最小割这个蹩脚的定义只能解决这些堵截类问题了。拆点网络流,注意s,t都有权,拆的时候和笨笨熊区别开。继续加个超级汇即可。

UVA 11248,最大流/最小割的综合题。首先先让你判断是否能达到指定流量,增广到指定流量break即可。第二,如果不能达到指定流量,问你是否能修改一条边的容量,达到指定流量。第二步比较麻烦,首先修改的边至少得是割里面的,这是第一层剪枝。我用随机数据跑所有边,结果时间是10倍,说明这个剪枝很重要。其次,就是不要把流量清掉,直接继续从s增广,记得备份原始图,每次修改后覆盖回来。在其次,就是数据结构问题,如果你存图的数据结构支持重边(就是相同的边容量合并),那么这样就不用费事把割里边取出修改,直接加一条重边即可。还有个小注意点,虽说是不清流量,但是加边后重新增广的流量是多出的流量,而不是现有的总流量。

最麻烦的是割的记录问题,尤其是ISAP的记录,这里贴下LRJ的代码。反正我是没看懂orz。
 
vector<Edge> Mincut(LL s,LL t)   // call this after maxflow{    BFS(s,t);    vector<Edge> ans;    for(int i = 0; i < edges.size(); i++)    {        Edge& e = edges[i];        if(!vis[e.from] && vis[e.to] && e.cap > 0) ans.push_back(e);    }    return ans;}
View Code

3. 最小费用流问题

你可以认为费用流=最大流+最短路径。原因是最小费用流的代码真的是这么写的。没有烦人的Dinic、ISAP,就是在EK的基础上改了一下, 毕竟最大流的增广路可以一次BFS,但是加上最短路,每次增广后图的结构就会改变,所以每次都得BFS。退化了orz。 但是代码简单不代表用起来简单,最小费用流的重点在于可以求解最优化问题,比如某些动态规划,俗称的2B网络流建图方式。网络流的精髓在于最小费用流,难 点就在建图上。

模板如下。
 
#include "cstdio"#include "vector"#include "cstring"#include "queue"using namespace std;#define maxn 210#define inf 99999999struct Edge{    int from,to,cap,flow,cost;    Edge(int FROM,int TO,int CAP,int FLOW,int COST):from(FROM),to(TO),cap(CAP),flow(FLOW),cost(COST) {}};vector<int> G[maxn];vector<Edge> edges;int d[maxn],p[maxn],a[maxn];bool inq[maxn];void addedge(int from,int to,int cap,int cost){    edges.push_back(Edge(from,to,cap,0,cost));    edges.push_back(Edge(to,from,0,0,-cost));    int m=edges.size();    G[from].push_back(m-2);    G[to].push_back(m-1);}bool spfa(int s,int t,int &flow,int &cost){    memset(p,-1,sizeof(p));    for(int i=s;i<=t;i++) d[i]=(i==s?0:inf);    a[s]=inf;    queue<int> Q;Q.push(s);    while(!Q.empty())    {        int u=Q.front();Q.pop();        inq[u]=false;        for(int v=0;v<G[u].size();v++)        {            Edge e=edges[G[u][v]];            if(e.cap>e.flow&&d[u]+e.cost<d[e.to])            {                d[e.to]=d[u]+e.cost;                a[e.to]=min(a[u],e.cap-e.flow);                p[e.to]=G[u][v];                if(!inq[e.to])                {                    inq[e.to]=true;                    Q.push(e.to);                }            }        }    }    if(d[t]==inf) return false;    flow+=a[t];    cost+=d[t]; //很多人这里写着a[t]*d[t]是很坑的,最早的费用=权*流量,但不是永远都是这个,当然对于cap=1就无所谓了。    int u=t;    while(u!=s)    {        edges[p[u]].flow+=a[t];        edges[p[u]^1].flow-=a[t];        u=edges[p[u]].from;    }    return true;}
View Code

 

@费用流与建模

POJ 2195 经典的匹配费用流问题,建模的方案如下: 超级源点连所有X元素(人),费用0,流量1。X-Y有边则连,费用为权,流量随意(1或inf皆可)。所有Y(房子)连超级汇点,费用0,流量1。

POJ2135 只能存在一个方向的无向图,求个环,费用最小。这题是HDU 1853的弱化版,只要一个环足矣。有种偷懒不拆点的建图方式,利用本题只能存在一个方向,建无向图,费用为cost,容量为1,超级源点连源点,费用0容量2,汇点连超级汇点,费用0容量2即可。等于从s出发向t增广上下两路增广(容量为2的含义),解决了成环问题。

HDU 1853 有向图多环了。先拆点,拆点的方式与点权拆点不同。如果u、v有边,u连v‘,费用cost,容量1。超级源连所有本点,超级汇连所有影子点,费用0,容量1。这里最关键的是把权加在这条边上,但是本点影子点却没有连接。画个图就清楚了,将本点作为入点,影子点作为出点,u连v’将费用从影子点带到汇点。最大流量是有意义的,代表几个点被访问了。flow=n才能保证所有点全访问过,否则输出-1.

网络流