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

    网络流

    网络流问题常见的求解目标有最大流(最小割)、最小费用最大流、上下界可行流等

    最小割

    最大流还有一个很重要的应用,就是求最小割,以下是一些定理,其实这些和二分图匹配里面的有点相似:

    最小割 = 最大流

    最大点权覆盖集 = 最小割

    最小点权独立集 = 总权值 - 最大点权覆盖集

    最小割的定义:把网络划分成两个集合,s,t使得在s集合和在t集合中的任意两点互相不相连,去掉的边就是割边,而这些割边代价总和就是割,最小割就是代价最小的划分方法

    一类最小割的题目还是挺明显,如果是要把存在点划分两个集合,求最小代价之类的,就很明显是最小割了

    最小割有一些挺经典的模型:

    1、平面图求最小割:

    这个做法就是,把平面每一部分面积当成点,然后相邻块连边,然后增设源点s和汇点t,分别在原来的入口出口的另一对角线上,和相应两部分边连边,这时候,每一个最小割,其实就是一个s到t的路径,那么求最短路就是最小割了

    2、最小权闭合

    这个做法是源点s连向正权值,负权值连向汇点t,之间关系连边容量INF,求出最小割之后,这个最小割就是最少损失,然后总权值 - 最小割得到的就是最大权闭合图的权值

    关于最小割输出路径

    根据题意,如果要s集合尽量多,就从t集合dfs,反之则从s集合dfs,不经过没有容量的边即可

    费用流

    一类K覆盖问题:

    这类问题一般表现为,一个区间或者一些点,每个可以最多被覆盖k次,然后有一些特殊的边可以走,但是只能走一次,这时候要求k覆盖后的最大权值

    其实就是费用流,建边把特殊边建起来,对应相应费用,然后其他边直接相连,费用为0,注意由于是要求最大代价,而算法是求最小费用,其实和KM匹配求最小一样的思路,把边权弄成负数,跑一下即可

    网络流的一些特殊问题

    上下界网络流:

    无源无汇有上下界最大流:

    这个要根据流量平衡来搞,建图先把边容量定成上限up - 下限down,然后每一个点,记录下流入流量和流出流量,然后设一个超级源s,超级汇t,s连接流量正的点,流量负的点连向t,然后跑最大流,跑完之后如果从s流出的流量都是满流,就是有解,每个边的真实流量就为当前边流量,加上原来的下限

    有源有汇有上下界最大流:

    建图方法一致,不过要多连上一条t->s容量为INF的边,这样跑一下最大流,t->s的流量就是答案

    有源有汇有上下界最小流:

    也是一样,不过t->s先不连,先求一次最大流,然后在连t->s,在做一次最大流把残余流量充分利用,然后t->s的流量就是答案

    分层网络流:

    这类题,以时间为单位,这样对于每个时间点就要建一层结点,时间上限不是很大的话,就可以每多一个时间点,就多一层结点,在原来的图上继续增广

    混合图找欧拉回路:

    欧拉回路入度等于出度,然后无向图先任意定向,然后记录每个点的度数和,度数和 / 2就是需要调整的边数,然后把源点连向正的,负的连向汇点,然后中间的边就是连无向边,因为只有无向边可以调整,然后跑一下最大流即可

    增加哪些边会使得最大流增加:

    这类问题其实就是对于满流的边,如果左边源点到他和他到汇点,能有一个残量网络,这条边就是可以增加的,利用两个dfs,分别从源点汇点出发即可

    最大密度子图:

    先要记录下每个结点的度数

    利用二分搜索来搜索答案g,然后根据这个建图判断,判断的方式为:

    源点与原图中每一个点连一条容量为m的边。原图中每一个点与汇点连一条容量为m+2*g-度数的边,再将原图中的无向边拆成两条有向边,容量都设为1.然后对此图求最大流,最后将(n*m-maxflow)/2 与0比较大小,如果它大于0,l = g,

    否则r = g

    POJ 3281 Dining

    题意:

    一个人有喜欢的饮料跟食物,现在给出这些关系,问最多能有多少人得到喜欢的饮料跟食物

    思路:

    网络流的题目往往难在建模,我们很容易想到的一种建模方法就是源点-食物-人-饮料-汇点

    但是这样的话可能一个人可以满足多次,所以为了加上人的这个限制条件,就需要将人拆点,并连上一条权值为1的边

    拆点也是对于点限制的很好的解决方法

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<queue>
     using namespace std;
     const int inf=1<<30;
    struct Graph{
        static const int M=100010,N=10010;
        int dis[N],cur[N],head[N],cnt;
        void Grap(){
            cnt=-1;
            memset(head,-1,sizeof head);
        }
        struct edge{
            int to,val,next;
        }e[M<<1];
        void _add(int u,int v,int val){
            cnt++;
            e[cnt].next=head[u];
            e[cnt].to=v;
            e[cnt].val=val;
            head[u]=cnt;
        }
        void add(int u,int v,int val){
            _add(u,v,val);
            _add(v,u,0);
        }
        bool bfs(int s,int t){
            queue<int> q;
            memset(dis,-1,sizeof dis);
            dis[s]=0;
            q.push(s);
            while(!q.empty()){
                int u=q.front();
                q.pop();
                for(int i=head[u];i!=-1;i=e[i].next){
                    int v=e[i].to;
                    if(dis[v]==-1&&e[i].val>0){
                        dis[v]=dis[u]+1;
                        q.push(v);
                    }
                }
            }
            return dis[t]!=-1;
        }
        int dfs(int s,int t,int maxflow){
            if(s==t)return maxflow;
            int res=0;
            for(int& i=cur[s];i!=-1;i=e[i].next){
                int v=e[i].to;
                if(dis[v]!=dis[s]+1||e[i].val<=0||res>=maxflow)continue;
                int f=dfs(v,t,min(e[i].val,maxflow-res));
                e[i].val-=f;
                e[i^1].val+=f;
                res+=f;
            }
            return res;
        }
        int Dinic(int s,int t){
            int ans=0;
            while(bfs(s,t)){
                memcpy(cur,head,sizeof head);
                ans+=dfs(s,t,inf);
            }
            return ans;
        }
    }mode;
     int main()
     {
         mode.Grap();
         int n,a,b;
         scanf("%d%d%d",&n,&a,&b);
         for(int i=1;i<=n;i++){
             int f,d,x;
             scanf("%d%d",&f,&d);
             for(int j=1;j<=f;j++){
                 scanf("%d",&x);
                 mode.add(x,a+i,1);
             }
            for(int j=1;j<=d;j++){
                scanf("%d",&x);
                mode.add(a+n+i,2*n+a+x,1);
            }    
        }
        for(int i=1;i<=n;i++) mode.add(a+i,a+n+i,1);
        for(int i=1;i<=a;i++) mode.add(0,i,1);
        for(int i=1;i<=b;i++) mode.add(2*n+a+i,2*n+a+b+1,1);
        cout<<mode.Dinic(0,2*n+a+b+1)<<endl;
        return 0;
     }
    View Code

    POJ 1087 A Plug for UNIX

    题意:

    有n个不同型号的插座(每种型号只有1个),m个不同的用电器且有着对应的插座型号(不同用电器对应的插座型号可能相同),同时有k种插座替代关系,例如A型号插座可以替代B型号插座(但B不一定能替代

    A)(插座型号和用电器均用字符串表示)。问最少有多少用电器没有插座用。

    思路:

    建立源点-插座-用电器-汇点的网络

    对于转换插座的话,可以直接在插座之间连上新的边,容量为inf(因为使用次数不限)

    最后跑一个最大流

    反思:在建图上出了很多的错误,建图连边的话不一定要很紧凑,这样建边不容易出错

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<map>
    #include<set>
    #include<string>
     using namespace std;
     const int inf=1<<30;
     int n,m,k;
     const int maxn=1e4+10;
     string t1,t2;
     map<string,int> m1;
     int cnt=0;
    struct Graph{
        static const int M=100010,N=10010;
        int dis[N],cur[N],head[N],cnt;
        void Grap(){
            cnt=-1;
            memset(head,-1,sizeof head);
        }
        struct edge{
            int to,val,next;
        }e[M<<1];
        void _add(int u,int v,int val){
            cnt++;
            e[cnt].next=head[u];
            e[cnt].to=v;
            e[cnt].val=val;
            head[u]=cnt;
        }
        void add(int u,int v,int val){
            _add(u,v,val);
            _add(v,u,0);
        }
        bool bfs(int s,int t){
            queue<int> q;
            memset(dis,-1,sizeof dis);
            dis[s]=0;
            q.push(s);
            while(!q.empty()){
                int u=q.front();
                q.pop();
                for(int i=head[u];i!=-1;i=e[i].next){
                    int v=e[i].to;
                    if(dis[v]==-1&&e[i].val>0){
                        dis[v]=dis[u]+1;
                        q.push(v);
                    }
                }
            }
            return dis[t]!=-1;
        }
        int dfs(int s,int t,int maxflow){
            if(s==t)return maxflow;
            int res=0;
            for(int& i=cur[s];i!=-1;i=e[i].next){
                int v=e[i].to;
                if(dis[v]!=dis[s]+1||e[i].val<=0||res>=maxflow)continue;
                int f=dfs(v,t,min(e[i].val,maxflow-res));
                e[i].val-=f;
                e[i^1].val+=f;
                res+=f;
            }
            return res;
        }
        int Dinic(int s,int t){
            int ans=0;
            while(bfs(s,t)){
                memcpy(cur,head,sizeof head);
                ans+=dfs(s,t,inf);
            }
            return ans;
        }
    }mode;
    int get(string s)
    {
        if(m1[s]==0) m1[s]=++cnt;
        return m1[s];
    }
    int main()
    {
        mode.Grap();
        int s=0,t=500;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>t1;
            mode.add(s,get(t1),1);    
        }
        cin>>m;
        for(int i=1;i<=m;i++){
            cin>>t1>>t2;
            mode.add(get(t2),200+i,1);
            mode.add(200+i,t,1);
        }
        cin>>k;
        for(int i=1;i<=k;i++){
            cin>>t1>>t2;
            mode.add(get(t2),get(t1),inf);
        }
        cout<<m-mode.Dinic(s,t)<<endl;
        return 0;
    }
    View Code

    POJ  2195 Going Home

    题意:

    有n个人,n座房子,每个人到不同的房子有不同的距离,现在给每个人分配一座房子求所有人到对应房子的最短距离

    思路:

    方法①很明显这是一个二分图,题目要求求的是一个最小权值完备匹配,所以可以直接将权值取负,利用KM算法求得最小值

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<map>
    #include<queue>
    #include<iostream>
    using namespace std;
    const int N=205,INF=0x3f3f3f3f;
    struct node{
        int x,y;
    }g[N],f[N];
    int x,y,cnt1,cnt2;
    char mp[N][N];
    map<int,int> m1,m2;
    int n,m,t,u,v,w[N][N],la[N],a,lb[N],mat[N],d,upd[N];
    bool va[N],vb[N];
    bool dfs(int u) {
        va[u]=true; 
        for(int v=1;v<=n;v++) {
            if(vb[v]) continue;
            if (la[u]+lb[v]-w[u][v]==0) {
                vb[v]=true;
                if (!mat[v]||dfs(mat[v])) {
                    mat[v]=u;
                    return true;
                }
            } else upd[v]=min(upd[v],la[u]+lb[v]-w[u][v]);
        } 
        return false;
    }
    int KM() {
        memset(mat,0,sizeof(mat));
        for (int i=1;i<=n;i++) {
            la[i]=-INF;
            lb[i]=0;
            for(int j=1;j<=n;j++) {
                la[i]=max(la[i],w[i][j]); 
            }    
        }
        for(int i=1;i<=n;i++) {
            while(1){
                memset(va,false,sizeof(va));
                memset(vb,false,sizeof(vb));
                for(int j=1;j<=n;j++) upd[j]=INF;
                if(dfs(i)) break;
                d=INF;
                for(int j=1;j<=n;j++) if(!vb[j]) d=min(d,upd[j]);
                for (int j=1;j<=n;j++) {
                    if(va[j]) la[j]-=d;
                    if(vb[j]) lb[j]+=d;
                } 
            }
        }
        int ans=0;
        for (int i=1;i<=n;i++) ans+=w[mat[i]][i];
        return -ans; 
    }
    int main() {
        while(scanf("%d%d",&x,&y)&&x||y){
            memset(w,0,sizeof(w));
            cnt1=cnt2=0;
            for(int i=0;i<x;i++){
                scanf("%s",mp[i]);
                for(int j=0;j<y;j++){
                    if(mp[i][j]=='m') g[++cnt1].x=i,g[cnt1].y=j;
                    if(mp[i][j]=='H') f[++cnt2].x=i,f[cnt2].y=j;
                }
            }
            for(int i=1;i<=cnt1;i++)
                for(int j=1;j<=cnt1;j++)
                    w[i][j]=-(abs(g[i].x-f[j].x)+abs(g[i].y-f[j].y));
            n=cnt1;
            cout<<KM()<<endl;
        }
        return 0;
    } 
    View Code
  • 相关阅读:
    第五次站立会议
    第四次站立会议
    第三次晚间站立总结会议
    易校小程序典型用户需求分析
    第三次站立会议
    第二次晚间站立总结会议
    第二次站立会议
    第一次晚间站立总结会议
    MyBatis注解
    延迟加载与缓存
  • 原文地址:https://www.cnblogs.com/overrate-wsj/p/12641566.html
Copyright © 2011-2022 走看看