网络流

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 100000000
struct 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 99999999
struct 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.
posted @ 2014-10-02 18:04  Physcal  阅读(1136)  评论(0编辑  收藏  举报