• 网络流


    网络流

    ​ ————重在建模

    网络,有向图,源点s,汇点t

    最大流

    Edmonds−karp(EK)增广路算法

    (O(nm^2)) $ 10^3 — 10^4 $

    不断用BFS寻找增广路并不断更新最大流量值,直到网络上不存在增广路为止

    在BFS寻找一条增广路时,我们只需要考虑剩余流量不为0的边,然后找到一条从S到T的路径,同时计算出路径上各边剩余容量值的最小值dis,则网络的最大流量就可以增加dis(经过的正向边容量值全部减去dis,反向边全部加上dis)

    注意tot=1;

    tot是从2开始,因为1^1=0,而没有0这条边,2的反边是3,4的反边是5

    建边的时候见单向边,然后再建一条 w=0 的反边

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    typedef long long LL;
    const int N=510005;
    int n,m,s,t;
    int tot=1,hd[N];
    
    struct edge{
        int to,nxt;
        LL w;
    }e[N];
    inline void add(int x,int y,LL z) {
        e[++tot].to=y;e[tot].w=z;e[tot].nxt=hd[x];hd[x]=tot;
    }
    
    bool vis[N];
    int pre[N];
    LL dis[N],ans;
    inline bool bfs() {
        for(int i=1;i<=n;i++) vis[i]=0;
        queue<int>q;
        q.push(s);
        vis[s]=1;
        dis[s]=99999999999;
        while(!q.empty()) {
            int x=q.front();q.pop();
            for(int i=hd[x];i;i=e[i].nxt) {
                int y=e[i].to;
                if(vis[y]) continue;
                if(e[i].w<=0) continue;//!!!
                dis[y]=min(dis[x],e[i].w);
                pre[y]=i;
                q.push(y);
                vis[y]=1;
                if(y==t) return 1;
            }
        }
        return 0;
    }
    
    inline void update() {
        int x=t;
        ans+=dis[t];
        while(x!=s) {
            int v=pre[x];
            e[v].w-=dis[t];
            e[v^1].w+=dis[t];
            x=e[v^1].to;
        }
    }
    int flag[2505][2505];
    int main() {
        scanf("%d%d%d%d",&n,&m,&s,&t);
        int x,y;LL z;
        for(int i=1;i<=m;i++) {
            scanf("%d%d%lld",&x,&y,&z);
            if(!flag[x][y]) {
                add(x,y,z);
                add(y,x,0);   
                flag[x][y]=tot;         
            }   
            else e[flag[x][y]-1].w+=z;
        }
        while(bfs()) update();
        printf("%lld
    ",ans);
        return 0;
    }
    
    

    Dinic

    (O(n^2m)) —— $ 10^4 — 10^5 $

    食用这篇blog更易理解

    基于对EK的优化,EK每次bfs只能找到一条增广路,Dinic就通过dfs优化它(见下面)

    BFS 出图的层次,用 d[] 数组表示它的层次,即S到x最少需要经过的边数

    在DFS中,从S开始,每次我们向下一层次随便找一个点,直到到达T,然后再一层一层回溯回去,继续找这一层的另外的点再往下搜索,这样就满足了我们同时求出多条增广路的需求

    当前弧优化

    其实就是走过的边dfs返回后不再走。。。

    具体来说,就是 开一个now[] ,一开始now[]=head[],在dfs中,把now[x]赋值为 i ,也就是下一条边,这样回溯回来的时候会直接走下一条边,而不是重复搜之前搜的

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    typedef long long LL;
    const int N=510005;
    const int inf=0x3f3f3f3f;
    int n,m,s,t;
    int hd[N],nxt[N],to[N],tot=1;
    LL ans,w[N];
    inline void add(int x,int y,LL z) {
        to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
    }
    
    LL c[N];
    int now[N];
    inline bool bfs() {
        for(int i=1;i<=n;i++) c[i]=inf;
        queue<int>q;
        q.push(s);
        c[s]=0;
        now[s]=hd[s];
        while(!q.empty()) {
            int x=q.front();q.pop();
            for(int i=hd[x];i;i=nxt[i]) {
                int y=to[i];
                if(c[y]<inf||w[i]<=0) continue;
                c[y]=c[x]+1;
                q.push(y);
                now[y]=hd[y];
                if(y==t) return 1;
            }        
        }
        return 0;
    }
    
    inline LL dfs(int x,LL rest) {
        if(x==t) return rest;
        LL sum=0,k;
        for(int i=now[x];i&&rest;i=nxt[i]) {//rest需>0
            now[x]=i;//当前弧优化
            int y=to[i];
            if(w[i]<=0||c[y]!=c[x]+1) continue;//
            k=dfs(y,min(rest,w[i]));
            if(!k) c[y]=inf;
            w[i]-=k;w[i^1]+=k;
            sum+=k;rest-=k;
        }
        return sum; 
    }
    
    int main() {
        scanf("%d%d%d%d",&n,&m,&s,&t);
        LL z;
        for(int i=1,x,y;i<=m;i++) {
            scanf("%d%d%lld",&x,&y,&z);
            add(x,y,z);
            add(y,x,0);
        }
        while(bfs()) ans+=dfs(s,inf);
        printf("%lld
    ",ans);
        return 0;
    }
    

    更牛B的算法

    题:

    模板

    模板题1

    模板题2

    模板+输出流的路径

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int N=1e5+5; 
    const int inf=0x3f3f3f3f;
    inline int read() {
        char ch=getchar();
        while(!isalpha(ch)) ch=getchar();
        return ch=='Y';
    }
    int n,m;
    bool mp[55][55];
    int hd[N],from[N],to[N],nxt[N],w[N],tot=1;
    inline void add(int x,int y,int z) {
        to[++tot]=y;from[tot]=x;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
    }
    void add_edge(int x,int y,int z) {
        add(x,y,z),add(y,x,0);
    }
    
    int s,t,ans;
    int c[N],now[N];
    bool bfs() {
        for(int i=1;i<N;i++) c[i]=inf;
        queue<int>q;
        q.push(s);
        c[s]=0;
        now[s]=hd[s];
        while(!q.empty()) {
            int x=q.front();q.pop();
            for(int i=hd[x];i;i=nxt[i]) {
                int y=to[i];
                if(w[i]==0||c[y]<inf) continue;
                c[y]=c[x]+1;
                q.push(y);
                now[y]=hd[y];
                if(y==t) return 1;
            }
        }
        return 0;
    }
    
    int dfs(int x,int rest) {
        if(x==t) return rest;
        int sum=0,k;
        for(int i=now[x];i&&rest;i=nxt[i]) {
            now[x]=i;
            int y=to[i];
            if(w[i]==0||c[y]!=c[x]+1) continue;
            k=dfs(y,min(rest,w[i]));
            if(!k) c[y]=inf;
            w[i]-=k;w[i^1]+=k;
            sum+=k;rest-=k;
        }
        return sum;
    }
    int main(){
        scanf("%d%d",&m,&n);
        int x,y;
        while(1) {
            scanf("%d%d",&x,&y);
            if(x==-1&&y==-1) break;
            add_edge(x,y,1);
        }
        s=0,t=n+1;    
        for(int i=1;i<=m;i++)
            add_edge(s,i,1);
        for(int i=m+1;i<=n;i++)
            add_edge(i,t,1);
        while(bfs()) ans+=dfs(s,inf);
        printf("%d
    ",ans);
        for(int i=2;i<=tot;i+=2) {
            if(to[i]==s||to[i^1]==s) continue;
            if(to[i]==t||to[i^1]==t) continue;
            if(w[i^1]) printf("%d %d
    ",from[i],to[i]);
        }
    	return 0;
    }
    

    最小割

    网络图上每条边有边权,移除边权和最小的一组边使得源点和汇点不连通

    最小割=最大流

    Description

    •有m个男生和n个女生,每个人有一个智商值,其中有一些男女生存在“交往过密”的现象(一个人有可能和多个异性“交往过密”)

    •现在要从中选出若干人组成一个班,现在你是教育处主任,你不希望看到这个班中有“交往过密”现象,同时你非常关心升学率,希望最大化这个班的智商值之和,求这个最大值

    •n,m≤10000,智商值非负

    Solution

    男女间连正无穷,s到男连男智商,女到t连女智商,跑最小割

    技巧

    边点转化——拆点/拆边

    Poj 1966

    最小费用最大流

    •费用流:边上还有一些费用,要求在流量最大的前提下,让每条边的流量*费用之和最大/最小

    bfs改成spfa,+1改成+val[i]

    注意dfs 入时vis[x]=1;回溯时vis[x]=0

    #include <iostream>
    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <cmath>
    #include <queue>
    using namespace std;
    inline int read() {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int n,m,s,t,ans=0,cost=0;
    const int N = 510005;
    const int inf = 0x3f3f3f3f;
    int hd[N],to[N],nxt[N],w[N],val[N],tot=1;
    inline void add(int x,int y,int z,int f) {
        to[++tot]=y;w[tot]=z;val[tot]=f;nxt[tot]=hd[x];hd[x]=tot;
    }
    
    bool vis[N];
    int now[N],c[N];
    inline bool spfa() {
        for(int i=1;i<=N;i++) c[i]=inf;
        queue<int>q;
        c[s]=0;
        q.push(s);
        now[s]=hd[s];
        while(!q.empty()) {
            int x=q.front();q.pop();
            vis[x]=0;
            for(int i=hd[x];i;i=nxt[i]) {
                int y=to[i];
                if(w[i]<=0) continue;
                if(c[y]>c[x]+val[i]) {
                    c[y]=c[x]+val[i];
                    now[y]=hd[y];
                    if(!vis[y]) vis[y]=1,q.push(y);
                }
            }        
        }
        return (c[t]!=inf);
    }
    
    inline int dfs(int x,int rest) {
        if(x==t) return rest;
        int sum=0,k;
        vis[x]=1;
        for(int i=now[x];i&&rest;i=nxt[i]) {
            now[x]=i;
            int y=to[i];
            if(c[y]!=c[x]+val[i] || w[i]<=0 || vis[y]) continue;
            k=dfs(y,min(rest,w[i]));
            if(!k) c[y]=inf;
            w[i]-=k;w[i^1]+=k;
            sum+=k; rest-=k;
            cost+=k*val[i];
        }
        vis[x]=0;//回溯
        return sum;
    }
    int main() {
        n=read();m=read();
        s=read();t=read();
        for(int i=1,x,y,z,f;i<=m;i++) 
            x=read(),y=read(),z=read(),f=read(),add(x,y,z,f),add(y,x,0,-f);
    
        while(spfa()) 
            ans+=dfs(s,inf);
    
        printf("%d %d
    ",ans,cost);
        return 0;
    }
    

    problems

    晨跑

    题意就是最小费用最大流,边和点的限制都是只能用一次,边可以用流量限制,点呢?应用上面提到的有向图拆点技巧,拆成入点和出点,中间连一条限制为 1(费用为0) 的边,以边代点

    #include <iostream>
    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <queue>
    using namespace std;
    inline int read() {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int n,m,s,t,ans=0,cost=0;
    const int N = 510005;
    const int inf = 0x3f3f3f3f;
    int hd[N],to[N],nxt[N],w[N],val[N],tot=1;
    inline void add(int x,int y,int z,int f) {
        to[++tot]=y;w[tot]=z;val[tot]=f;nxt[tot]=hd[x];hd[x]=tot;
    }
    
    bool vis[N];
    int now[N],c[N];
    inline bool spfa() {
        for(int i=1;i<=N;i++) c[i]=inf;
        queue<int>q;
        c[s]=0;
        q.push(s);
        now[s]=hd[s];
        while(!q.empty()) {
            int x=q.front();q.pop();
            vis[x]=0;
            for(int i=hd[x];i;i=nxt[i]) {
                int y=to[i];
                if(w[i]==0) continue;
                if(c[y]>c[x]+val[i]) {
                    c[y]=c[x]+val[i];
                    now[y]=hd[y];
                    if(!vis[y]) vis[y]=1,q.push(y);
                }
            }        
        }
        return (c[t]!=inf);
    }
    
    inline int dfs(int x,int rest) {
        if(x==t) return rest;
        int sum=0,k;
        vis[x]=1;
        for(int i=now[x];i&&rest;i=nxt[i]) {
            now[x]=i;
            int y=to[i];
            if(c[y]!=c[x]+val[i] || w[i]==0 || vis[y]) continue;
            k=dfs(y,min(rest,w[i]));
            if(!k) c[y]=inf;
            w[i]-=k;w[i^1]+=k;
            sum+=k; rest-=k;
            cost+=k*val[i];
        }
        vis[x]=0;
        return sum;
    }
    int main() {
        n=read();m=read();
        s=1+n;t=n;
        for(int i=1,x,y,z;i<=m;i++) {
            x=read(),y=read(),z=read();
            add(x+n,y,1,z);//
            add(y,x+n,0,-z);//
        }
        for(int i=1;i<=n;i++) 
            add(i,i+n,1,0),add(i+n,i,0,0);//
    
        while(spfa()) 
            ans+=dfs(s,inf);
    
        printf("%d %d
    ",ans,cost);
        return 0;
    }
    

    方格取数

    方法 1,dp,我担心的,就是一个点会被两条路径重复经过,注意到如果这样的话那么两条 路径走到这个点所用步数相同,那么可以 $dp[i][j][k][l] $对这两条路径同时 dp,同时一步一步 走。

    方法 2,经过一个点,要么取一次,要么不取,这“仅取一次”是个限制 注意到一条路径可以等效为“一条流量” 一个点有两种入边,一种是不取数,只经过它,那么费用为 0,不需要流量限制(因为可以 是两条路径的交点,不仅经过一次) 另一种是取数,但是这种途径只能使用一次,因此流量限制为 1,费用为数 ,为了使其仅有两条路径,我把源点到左上角的点的流量连成 2 可以发现,这个建图思路是对“走路径”模拟得到的。 按照这个思路,很多带有“限制”的东西都能被费用流所“模拟”

    跳舞

    一个显然的想法是,男女可以看做左右部点,再让他们分别建立一个辅助点表示我要跟不喜欢 的人去跳,这个辅助点要连流量为 k 的边,表示 k 的限制,对于不喜欢的男女,男辅助点要连女 辅助点,喜欢的男女,让男主点连女主点,其实类似二分图匹配 但是这样的思路是混乱的,我们要求最多能让所有人集体跳多少轮,如果跑最大流,就会让 一些人多跳,但是另一些人少跳 你发现了没有,对于这种“整体限制”的不好做的问题,很好的思路就是二分答案 :我二分能跳多少轮,然后用刚才建的网络去判断是不是能跳 mid 轮。 对于每个人,我不让他多跳(源、汇点分别向男女连流量限制为 mid 的边)也不能让她少跳 (让总流量最大,判断是不是 mid*n),这种二分+判断满流的方法也是常用的

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int N=1e5+5; 
    const int inf=0x3f3f3f3f;
    inline int read() {
        char ch=getchar();
        while(!isalpha(ch)) ch=getchar();
        return ch=='Y';
    }
    int n,k;
    bool mp[55][55];
    int hd[N],to[N],nxt[N],w[N],tot=1;
    inline void add(int x,int y,int z) {
        to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
    }
    void add_edge(int x,int y,int z) {
        add(x,y,z),add(y,x,0);
    }
    void clear() {
        tot=1;
        memset(hd,0,sizeof(hd));
    }
    
    int s,t;
    int c[N],now[N];
    bool bfs() {
        for(int i=1;i<N;i++) c[i]=inf;
        queue<int>q;
        q.push(s);
        c[s]=0;
        now[s]=hd[s];
        while(!q.empty()) {
            int x=q.front();q.pop();
            for(int i=hd[x];i;i=nxt[i]) {
                int y=to[i];
                if(w[i]==0||c[y]<inf) continue;
                c[y]=c[x]+1;
                q.push(y);
                now[y]=hd[y];
                if(y==t) return 1;
            }
        }
        return 0;
    }
    
    int dfs(int x,int rest) {
        if(x==t) return rest;
        int sum=0,k;
        for(int i=now[x];i&&rest;i=nxt[i]) {
            now[x]=i;
            int y=to[i];
            if(w[i]==0||c[y]!=c[x]+1) continue;
            k=dfs(y,min(rest,w[i]));
            if(!k) c[y]=inf;
            w[i]-=k;w[i^1]+=k;
            sum+=k;rest-=k;
        }
        return sum;
    }
    
    bool check(int x) {
        clear();
        int res=0;
        for(int i=1;i<=n;i++)   
            add_edge(s,i,x),add_edge(i+n,t,x),
            add_edge(i,i+n*2,k),add_edge(i+n*3,i+n,k);
        for(int i=1;i<=n;i++)   
            for(int j=1;j<=n;j++)
                if(mp[i][j]) add_edge(i,j+n,1);
                else add_edge(i+n*2,j+n*3,1);
        while(bfs()) res+=dfs(s,inf);
        return res==n*x;
    }
    int main(){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) 
            for(int j=1;j<=n;j++) 
                mp[i][j]=read();
        s=0,t=n*4+1;    
    	int l=0,r=n,ans;
        while(l<=r) {
            int mid=l+r>>1;
            if(check(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d
    ",ans);
    	return 0;
    }
    
    

    https://www.luogu.com.cn/blog/Multifuctional/fu-zai-ping-heng-wen-ti

  • 相关阅读:
    mysql数据库 --数据类型、约束条件
    并发编程 --线程
    并发编程 --进程
    MySQL数据库 --基础
    网络编程之 TCP-UDP的详细介绍
    网络编程之 OSI七层协议
    python 元类、单例模式
    python 面向对象_多态、内置方法、反射
    Python 面向对象_继承、组合
    简单工厂设计模式
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13520029.html
Copyright © 2020-2023  润新知