zoukankan      html  css  js  c++  java
  • 网络流算法与建模总结

    [2016-12-16]

    【算法】

    1.最大流

    容量限制:对于∀u,v∈V ,要求 f (u,v) ≤ c(u,v)。

    流量平衡:对于∀u∈V −{s,t},要求∑f(u,v)=0。 

    dinic

    1. 根据残量网络计算层次图。
    2. 在层次图中使用DFS沿阻塞流(不考虑反向弧时的极大流 层次图中的)进行增广直到不存在增广路
    3. 重复以上步骤直到无法增广

    使用当前弧优化

    int cur[N];
    int vis[N],d[N],q[N],head,tail;
    bool bfs(){
        memset(vis,0,sizeof(vis));
        memset(d,0,sizeof(d));
        head=tail=1;
        q[tail++]=s;d[s]=0;vis[s]=1;
        while(head!=tail){
            int u=q[head++];
            for(int i=h[u];i;i=e[i].ne){
                int v=e[i].v;
                if(!vis[v]&&e[i].c>e[i].f){
                    vis[v]=1;d[v]=d[u]+1;
                    q[tail++]=v;
                    if(v==t) return 1;
                }
            }
        }
        return 0;
    }
    int dfs(int u,int a){
        if(u==t||a==0) return a;
        int flow=0,f;
        for(int &i=cur[u];i;i=e[i].ne){
            int v=e[i].v;
            if(d[v]==d[u]+1&&(f=dfs(v,min(a,e[i].c-e[i].f)))>0){
                flow+=f;
                e[i].f+=f;
                e[((i-1)^1)+1].f-=f;
                a-=f;
                if(a==0) break;
            }
        }
        return flow;
    }
    int dinic(){
        int flow=0;
        while(bfs()){
            for(int i=s;i<=t;i++) cur[i]=h[i];
            flow+=dfs(s,INF);
        }
        return flow;
    }
    View Code

    【2017-01-24】还有一种优化,增广后还有a说明在这一层从u这个点不可能在增广了,设置d[u]=-1就可以了

    使用这个优化会快一点,貌似这个和当前弧用一个就可以,因为会带来常数问题

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    const int N=1e4+5,M=1e5+5,INF=1e9;
    inline int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    
    struct edge{
        int v,c,f,ne;
    }e[M<<1];
    int cnt,h[N];
    inline void ins(int u,int v,int c){
        cnt++;
        e[cnt].v=v;e[cnt].c=c;e[cnt].f=0;e[cnt].ne=h[u];h[u]=cnt;
        cnt++;
        e[cnt].v=u;e[cnt].c=0;e[cnt].f=0;e[cnt].ne=h[v];h[v]=cnt;
    }
    int cur[N],d[N],vis[N];
    int q[N],head,tail;
    bool bfs(){
        head=tail=1;
        memset(vis,0,sizeof(vis));
        d[s]=1;vis[s]=1;q[tail++]=s;
        while(head!=tail){
            int u=q[head++];
            for(int i=h[u];i;i=e[i].ne){
                int v=e[i].v;
                if(!vis[v]&&e[i].c>e[i].f){
                    vis[v]=1;d[v]=d[u]+1;
                    q[tail++]=v;
                    if(v==t) return true;
                }
            }
        }
        return false;
    }
    int dfs(int u,int a){
        if(u==t||a==0) return a;
        int flow=0,f;
        for(int &i=cur[u];i;i=e[i].ne){
            int v=e[i].v;
            if(d[v]==d[u]+1&&(f=dfs(v,min(e[i].c-e[i].f,a)))>0){
                flow+=f;
                e[i].f+=f;
                e[((i-1)^1)+1].f-=f;
                a-=f;
                if(a==0) break;
            }
        }
        if(a) d[u]=-1;
        return flow;
    }
    
    int dinic(){
        int flow=0;
        while(bfs()){
            for(int i=s;i<=t;i++) cur[i]=h[i];
            flow+=dfs(s,INF);
        }
        return flow;
    }
    View Code

    2.最小割

    最大流的对偶问题

    流网络G =(V,E)的割(cut)[S,T]将点集V划分为S和T(T =V −S)两个部分,

    使得源s∈S且汇t∈T。符号[S,T]代表一个边集合{ u,v | u,v ∈E,u∈S,v∈T}。

    穿过割 [S,T]的净流(net flow)定义为 f (S,T),割[S,T]的容量(capacity)定义为c(S,T),一般 记为c[S,T]。

    一个网络的最小割(minimum cut)也就是该网络中容量最小的割。 

    增广路算法结束时,所有还有流量(从s走)的点组成S,没有流量的点组成T

    最大流的流量就是最小割的容量

    3.最小费用最大流

    (1)spfa费用流

    用spfa找最短路来增广

    保存pre[i]和pos[i]分别是最短路中的父节点和入边

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int N=5005,M=5e4+5,INF=1e9;
    int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    int n,m,s,t,u,v,w,c;
    struct edge{
        int v,ne,c,f,w;
    }e[M<<1];
    int cnt,h[N];
    inline void ins(int u,int v,int c,int w){
        cnt++;
        e[cnt].v=v;e[cnt].c=c;e[cnt].f=0;e[cnt].w=w;
        e[cnt].ne=h[u];h[u]=cnt;
        cnt++;
        e[cnt].v=u;e[cnt].c=0;e[cnt].f=0;e[cnt].w=-w;
        e[cnt].ne=h[v];h[v]=cnt;
    }
    int d[N],pre[N],pos[N],q[N],head=1,tail=1,inq[N];
    inline void lop(int &x){if(x==N) x=1;else if(x==0) x=N-1;}
    bool spfa(){
        memset(d,127,sizeof(d));
        d[s]=0;pre[t]=-1;
        head=tail=1;
        memset(inq,0,sizeof(inq));
        q[tail++]=s;inq[s]=1;
        while(head!=tail){
            int u=q[head++];lop(head);inq[u]=0;
            for(int i=h[u];i;i=e[i].ne){
                int v=e[i].v,w=e[i].w;
                if(d[v]>d[u]+w&&e[i].c>e[i].f){
                    d[v]=d[u]+w;
                    pre[v]=u;
                    pos[v]=i;
                    if(!inq[v]){
                        if(d[v]<d[q[head]]) head--,lop(head),q[head]=v;
                        else q[tail++]=v,lop(tail);
                        inq[v]=1;
                    }
                }
            }
        }
        return pre[t]==-1?0:1;
    }
    void mcmf(){
        ll flow=0,cost=0;
        while(spfa()){
            int f=INF;
            for(int i=t;i!=s;i=pre[i]) f=min(f,e[pos[i]].c-e[pos[i]].f);
            flow+=f;
            cost+=f*d[t];
            for(int i=t;i!=s;i=pre[i]){
                e[pos[i]].f+=f;
                e[((pos[i]-1)^1)+1].f-=f;
            }
        }
        printf("%lld %lld",flow,cost);
    }
    int main(int argc, const char * argv[]) {
        n=read();m=read();s=read();t=read();
        for(int i=1;i<=m;i++){
            u=read();v=read();c=read();w=read();
            ins(u,v,c,w);
        }
        mcmf();
        return 0;
    }
    View Code

    (2)zkw费用流

    http://www.artofproblemsolving.com/community/c1368h1020435

    研究了好长时间.........

    修改D的做法和KM很想,但同时缺点明显,感觉没大有意义

    原始对偶算法貌似就是改了个多路增广,并且过程跟dinic还有点不一样

    貌似没有明显优化

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int N=5005,M=5e4+5,INF=1e9;
    int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
        return x*f;
    }
    int n,m,s,t,u,v,w,c;
    struct edge{
        int v,ne,c,f,w;
    }e[M<<1];
    int cnt,h[N];
    inline void ins(int u,int v,int c,int w){
        cnt++;
        e[cnt].v=v;e[cnt].c=c;e[cnt].f=0;e[cnt].w=w;
        e[cnt].ne=h[u];h[u]=cnt;
        cnt++;
        e[cnt].v=u;e[cnt].c=0;e[cnt].f=0;e[cnt].w=-w;
        e[cnt].ne=h[v];h[v]=cnt;
    }
    int d[N],q[N],head=1,tail=1,inq[N];
    inline void lop(int &x){if(x==N) x=1;else if(x==0) x=N-1;}
    bool spfa(){
        memset(d,127,sizeof(d));
        d[s]=0;
        head=tail=1;
        memset(inq,0,sizeof(inq));
        q[tail++]=s;inq[s]=1;
        while(head!=tail){
            int u=q[head++];lop(head);inq[u]=0;
            for(int i=h[u];i;i=e[i].ne){
                int v=e[i].v,w=e[i].w;
                if(d[v]>d[u]+w&&e[i].c>e[i].f){
                    d[v]=d[u]+w;
                    if(!inq[v]){
                        if(d[v]<d[q[head]]) head--,lop(head),q[head]=v;
                        else q[tail++]=v,lop(tail);
                        inq[v]=1;
                    }
                }
            }
        }
        return d[t]<INF;
    }
    int cur[N],ans,mark[N];
    int dfs(int u,int a){//printf("dfs %d %d
    ",u,a);
        if(u==t||a==0) return a;
        int flow=0,f;
        mark[u]=1;
        for(int &i=cur[u];i;i=e[i].ne){
            int v=e[i].v,w=e[i].w;
            if(d[v]==d[u]+w&&!mark[v]&&(f=dfs(v,min(a,e[i].c-e[i].f)))>0){
                ans+=f*w;
                flow+=f;
                e[i].f+=f;
                e[((i-1)^1)+1].f-=f;
                a-=f;
                if(a==0) break;
            }
        }
        return flow;
    }
    int zkw(){
        int flow=0;
        while(spfa()){
            mark[t]=1;
            while(mark[t]){
                memset(mark,0,sizeof(mark));
                for(int i=1;i<=n;i++) cur[i]=h[i];
                flow+=dfs(s,INF);
            }
        }
        return flow;
    }
    
    int main(int argc, const char * argv[]) {
        n=read();m=read();s=read();t=read();
        for(int i=1;i<=m;i++){
            u=read();v=read();c=read();w=read();
            ins(u,v,c,w);
        }
        int flow=zkw();
        printf("%d %d",flow,ans);
        return 0;
    }
    View Code

    【建模】

    1.公平分配问题

    把X分配给Y,一个X有两个Y可选,让分配到最多的最少

    二分图模型,X和Y构成二分图

    二分最多最少值mid

    s--1-->X--1-->Y--mid-->t

    看maxflow==|X|


    2.最大闭合子图

    定义一个有向图G = (V,E)的闭合图(closure)10是该有向图的一个点集,且该点集的所 有出边都还指向该点集。即闭合图内的任意点的任意后继也一定在闭合图中。 

    在原图点集的基础上增加源s和汇t;

    将原图每条有向边 u,v ∈E替换为容量为c(u,v)=∞的有向边u,v ∈EN;

    增加连接源s到原图每个正权点v(wv >0)的有向边s,v ∈EN,容量为c(s,v)=wv;

    增加连接原图每个负权点v(wv <0)到汇t的有向边v,t ∈EN,容量为c(v,t)=−wv 

    (这样下来两个边权都是正数)

    s--点权-->正权点----INF----负权点--|点权|-->t

    胡波涛论文中有详细证明

    我们也可以简单的思考最小割,要么(1)把s-->正u割了,要么(2)把负v-->t割了

    (1)相当于不选择这个u,他的后继就没必要选了,同时损失wu

    (2)相当于选择了u,同时选择了他的后继v,所以损失wv


    3.二分图最大匹配

    s--1-->X--1-->Y--1-->t 


    4.最小路径覆盖问题

    G 的最小路径覆盖是G 的所含路径条数最少的路径覆盖。

    有点逆向思考的感觉
    最差情况所有的点都是一条路径
    两个点连起来的话就少一条路径一个点
    拆成入点X和出点Y,构成二分图,ans=n-最大匹配数

    5.最小割挺套路的,想割什么东西连一条INF


    6.最大密度子图

    http://www.cnblogs.com/candy99/p/6430552.html

  • 相关阅读:
    HQ-day8 函数
    HQ-day7 随机数案例:随机出验证码,对照输入,判断是否正确
    HQ-day6 C#类
    获取用户IP 查找所在城市
    MVC 日常所用
    SQLServer·面试题
    关于WCF开发 相应流程注意事项
    存储过程更新
    存储过程删除举例
    存储过程添加举例
  • 原文地址:https://www.cnblogs.com/candy99/p/6104076.html
Copyright © 2011-2022 走看看