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

    最大流

    概念:

      网络流是指在一张图中,从源点(sp)到汇点(cp)每一时间点最大的流量。

    举例:

      假如我们想让尽量多的水从点s到点t,问题是1s最多有多少L水到达了t(我们假设水管中充满了水)

    如上图,边上的数字是水管每秒所能通过的水量。

    模板题:https://www.luogu.org/problem/P3376

      对于s->v1->t这条线路只能流过4,因为虽然s->v1可以流过5而v1->t只能流4.

    而s->v1->v2->t这条线路只能流过1(其实也可上流3,下流2),因为v1只剩了1.

    增广路:

      找到一条从源点至汇点的通路,流过一定流量。

    反向弧:

      对于每条弧,设q为它剩余的流量。

    并且需建一条反向弧,初始q为0,使得流动可以撤销。

    注意:q为可修改量,当流过流量q1时,正向弧q-=q1,反向弧q+=q1

    EK 时间复杂度O(n*m^2)

      EK的思路十分简单,就是不断的去找增广路,在找到后对该条增广路上的所有边的q做修改。

      实际实现过程中,我们利用bfs的方法来寻找增广路,用fl[u]表示增广路流到u的流量。

      fl[sp]=+无穷,然后如果fl[cp]==0就说明没有增广路了。

      然后对于每个点,可以记录下到达它的那条边,这样我们在确定修改增广路之后就可以直接从cp一直向前找到增广路。

    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int N=1e4+5,inf=1e9;
    struct E{
        int v,q,n;
    }e[N*20];
    int fir[N],s=1,n,m,sp,cp,fl[N],pre[N];
    queue<int>dl;
    void add(int u,int v,int q){
        e[++s].v=v;
        e[s].q=q;
        e[s].n=fir[u];
        fir[u]=s;
    }
    bool bfs(){
        memset(fl,0,sizeof(fl));
        dl.push(sp);fl[sp]=inf;
        while(!dl.empty()){
            int u=dl.front();dl.pop();
            for(int i=fir[u];i;i=e[i].n)
                if(e[i].q&&!fl[e[i].v]){
                    fl[e[i].v]=min(e[i].q,fl[u]);
                    pre[e[i].v]=i;
                    dl.push(e[i].v);
                }
        }
        return fl[cp];
    }
    int main(){
        int u,v,q,ans=0;
        scanf("%d%d%d%d",&n,&m,&sp,&cp);
        for(int i=1;i<=m;++i){
            scanf("%d%d%d",&u,&v,&q);
            add(u,v,q),add(v,u,0);
        }
        while(bfs()){
            ans+=fl[cp];
            for(int i=cp;i!=sp;i=e[pre[i]^1].v){
                e[pre[i]].q-=fl[cp];
                e[pre[i]^1].q+=fl[cp];
            }
        }
        printf("%d",ans);
        return 0;
    } 

     Dinic 时间复杂度 O(min(n^2/3,m^1/2)m)(可大致记为O(n^2*m))

      首先同样是进行增广路,dinic则不是任意选择增广。

      首先它进行bfs遍历所有可以遍历的点(剩余流量为0的边不可经过),并且标出层数(根据bfs时的遍历层数决定)

      然后在用dfs进行增广,此时在增广时,限制流向只能是第i层的点流向第i+1层。因此我们可以从一个点同时向多个点增广并且保证不产生回流。

      并且dfs中对于每个点访问邻接表是初始i=cur[u]而不是通常fir[u]保证已经被增广过的边不再被增广。因为此处cur[u]会随i改变,再次访问该节点时起始点也会改变。

    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int N=1e4+5,inf=1e9;
    struct E{
        int v,q,n;
    }e[N*20];
    int fir[N],s=1,n,m,sp,cp,dep[N],cur[N];
    queue<int>dl;
    void add(int u,int v,int q){
        e[++s].v=v;
        e[s].q=q;
        e[s].n=fir[u];
        fir[u]=s;
    }
    bool bfs(){
        memset(dep,0,sizeof(dep));
        dl.push(sp);dep[sp]=1;
        while(!dl.empty()){
            int u=dl.front();dl.pop();
            for(int i=fir[u];i;i=e[i].n)
                if(e[i].q&&!dep[e[i].v]){
                    dep[e[i].v]=dep[u]+1;
                    dl.push(e[i].v);
                }
        }
        return dep[cp];
    }
    int dfs(int u,int a){
        if(!a||u==cp) return a;
        int fl=0,f;
        for(int& i=cur[u];i;i=e[i].n)
            if(e[i].q&&dep[u]+1==dep[e[i].v]&&(f=dfs(e[i].v,min(a-fl,e[i].q)))){
                fl+=f;
                e[i].q-=f;
                e[i^1].q+=f;
            }
        return fl;
    }
    int main(){
        int u,v,q,ans=0;
        scanf("%d%d%d%d",&n,&m,&sp,&cp);
        for(int i=1;i<=m;++i){
            scanf("%d%d%d",&u,&v,&q);
            add(u,v,q),add(v,u,0);
        }
        while(bfs()){
            for(int i=1;i<=n;++i) cur[i]=fir[i];
            ans+=dfs(sp,inf);
        }
        printf("%d",ans);
        return 0;
    } 

    而且最大流还能求解二分图匹配问题。

    只需要建立一个超级源点s,s向每一个a集合的点连一条流量为1的边,建立超级汇点t,每一个b集合的点向t连接一条流量为1的边。

    并且从a集合每个点向它能匹配的b集合的点连一条流量为1的边。

    之后求最大流。

    例题:poj1469:http://poj.org/problem?id=1469

    题目大意:

    有t组数据

    a集合有p个点,b集合有m个点

    p行每行第一个数s s后为这一行对应的a集合的点能匹配的b集合的点

    问a集合的点能不能完美匹配。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=405;
    struct X
    {
        int v,sy,n,f;
    }x[70005];
    int n,s=1,pre[N],cs[N],q[N];
    bool vis[N];
    void add(int u,int v,int sy)
    {
        x[++s].v=v;
        x[s].n=x[u].f;
        x[x[u].f=s].sy=sy;
    }
    bool bfs()
    {
        memset(vis,0,sizeof(vis));
        vis[1]=q[0]=1;
        for(int t=0,w=1;t<w;++t)
            for(int i=x[q[t]].f;i;i=x[i].n)
                if(!vis[x[i].v]&&x[i].sy)
                {
                    vis[x[i].v]=1;
                    cs[x[i].v]=cs[q[t]]+1;
                    q[w++]=x[i].v;
                }
        return vis[n];
    }
    int dfs(int u,int a)
    {
        if(u==n||!a) return a;
        int fl=0,f;
        for(int &i=pre[u];i;i=x[i].n)
            if(cs[u]+1==cs[x[i].v]&&(f=dfs(x[i].v,min(a,x[i].sy))))
            {
                x[i].sy-=f;
                x[i^1].sy+=f;
                fl+=f;
                a-=f;
            }
        return fl;
    }
    int main()
    {
        int a,b,ans,t,c;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d",&a,&b);
            n=a+b+2;ans=0;
            s=1;
            memset(x,0,sizeof(x));
            for(int i=1;i<=a;++i) add(1,i+1,1),add(i+1,1,0);
            for(int i=1;i<=b;++i) add(i+a+1,n,1),add(n,i+a+1,0);        
            for(int i=1;i<=a;++i)
            {
                scanf("%d",&b);
                while(b--)
                {
                    scanf("%d",&c);
                    add(i+1,c+a+1,1);
                    add(c+a+1,i+1,0);
                }
            }
            while(bfs())
            {
                for(int i=1;i<=n;++i) pre[i]=x[i].f;
                ans+=dfs(1,1000000000);
            }
            ans==a?printf("YES
    "):printf("NO
    ");
        }
        return 0;
    }

     最小费用最大流

    在原来的基础上,每条边多了一个新的权值,费用w,这条边每次流过1的流量时,就会产生费用w。

    然后让你在维持最大流的前提下,使得费用尽可能小

    ek+spfa

    简单来说就是ek找增广路的时候,优先根据费用w所得最短路增广。

    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int N=5005;
    struct E{
        int v,q,n,w;
    }e[N*20];
    int fir[N],s=1,st,ed,fl[N],pre[N],dis[N];
    bool vis[N];
    queue<int>dl;
    void add(int u,int v,int q,int w){
        e[++s].v=v;
        e[s].n=fir[u];
        fir[u]=s;
        e[s].q=q;
        e[s].w=w;
    }
    bool bfs(){
        memset(fl,0,sizeof(fl));
        fl[st]=1e9;
        memset(dis,0x3f,sizeof(dis));
        dl.push(st);dis[st]=0;
        while(!dl.empty()){
            int u=dl.front();dl.pop();
            for(int i=fir[u];i;i=e[i].n)
                if(e[i].q&&dis[e[i].v]>dis[u]+e[i].w){
                    dis[e[i].v]=dis[u]+e[i].w; 
                    fl[e[i].v]=min(fl[u],e[i].q);
                    pre[e[i].v]=i^1;
                    if(!vis[e[i].v]){
                        vis[e[i].v]=1;
                        dl.push(e[i].v);
                    }
                }
            vis[u]=0;
        }
        return fl[ed];
    }
    int main(){
        int n,m,ans=0,ans1=0,u,v,q,w;
        scanf("%d%d%d%d",&n,&m,&st,&ed);
        while(m--){
            scanf("%d%d%d%d",&u,&v,&q,&w);
            add(u,v,q,w),add(v,u,0,-w);
        }
        while(bfs()){
            ans+=fl[ed];
            ans1+=fl[ed]*dis[ed]; 
            for(int i=ed;i!=st;i=e[pre[i]].v){
                e[pre[i]].q+=fl[ed];
                e[pre[i]^1].q-=fl[ed];
            }
        }
        printf("%d %d",ans,ans1);
        return 0;
    }

    dinic+spfa

    简单来说就是dinic标记层数改为根据最短路的dis数组标记(做spfa时反向跑,这样就能求出所有点到汇点的最短距离)

    然后dfs增广时只经过那些构成最短路的边

    #include<cstdio>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int N=5005,inf=1e9;
    typedef long long ll;
    struct E{
        int v,n,q,w;
    }e[N*20];
    int n,m,sp,cp,s=1,fir[N],dis[N],ans1,cur[N];
    ll ans;
    bool vis[N];
    queue<int>dl;
    void add(int u,int v,int q,int w){
        e[++s].v=v;
        e[s].q=q;
        e[s].w=w;
        e[s].n=fir[u];
        fir[u]=s;
    }
    bool spfa(){
        dl.push(cp);
        vis[cp]=1;
        memset(dis,0x3f,sizeof(dis));
        dis[cp]=0;
        while(!dl.empty()){
            int u=dl.front();dl.pop();
            for(int i=fir[u];i;i=e[i].n)
                if(e[i^1].q&&dis[e[i].v]>dis[u]-e[i].w){
                    dis[e[i].v]=dis[u]-e[i].w;
                    if(!vis[e[i].v]){
                        vis[e[i].v]=1;
                        dl.push(e[i].v);
                    }
                }
            vis[u]=0; 
        }
        return dis[sp]<1061109567;
    }
    int dfs(int u,int a){
        vis[u]=1;
        if(!a||u==cp) return a;
        int fl=0,f;
        for(int& i=cur[u];i;i=e[i].n)
            if(e[i].q&&!vis[e[i].v]&&dis[u]-e[i].w==dis[e[i].v]&&(f=dfs(e[i].v,min(e[i].q,a-fl)))){
                e[i].q-=f;
                e[i^1].q+=f;
                ans+=(ll)f*e[i].w;
                fl+=f;
            }
        return fl;
    }
    int main(){
        int u,v,q,w;
        scanf("%d%d%d%d",&n,&m,&sp,&cp);
        while(m--){
            scanf("%d%d%d%d",&u,&v,&q,&w);
            add(u,v,q,w),add(v,u,0,-w);
        }
        while(spfa()){
            vis[cp]=1;
            while(vis[cp]){
                memset(vis,0,sizeof(vis));
                for(int i=1;i<=n;++i) cur[i]=fir[i];
                ans1+=dfs(sp,inf);
            }
            memset(vis,0,sizeof(vis));
        }
        printf("%d %lld",ans1,ans);
        return 0;
    } 
  • 相关阅读:
    asp.net中的控件类型
    string、Empty和null三者的区别
    readonly和const的区别
    接口和抽象类的区别
    asp.net身份认证方式
    什么是继承
    dbca静默管理数据库&数据泵导出导入 大风起
    应用偶发性连接不上Oracle数据库的排查案例 大风起
    开源控件SourceGrid学习(附源代码)
    强类型与弱类型Dataset 与DataTable间的艰难选择
  • 原文地址:https://www.cnblogs.com/bzmd/p/7087608.html
Copyright © 2011-2022 走看看