zoukankan      html  css  js  c++  java
  • 网络流

    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
    ",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.
  • 相关阅读:
    知识搜索
    使用 getopt() 进行命令行处理
    【新提醒】夏新大v安卓4.1尝鲜最新更新版本发布(包含进步版)1124更新 大V综合交流区 360论坛
    搜狗知立方高调亮相 开启知识计算新时代
    socat: Linux / UNIX TCP Port Forwarder
    Crontab 每两周执行一次
    python 命令行解析 optionparser
    crontab jojo's blog--快乐忧伤都与你同在 BlogJava
    搜索引擎开始「实体搜索」新领域竞争,Google、百度分别发力实体搜索产品
    netcat(nc)的替代产品 Socat
  • 原文地址:https://www.cnblogs.com/neopenx/p/4004302.html
Copyright © 2011-2022 走看看