网络流问题

网络流

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

 

最小割

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

最小割 = 最大流

最大点权覆盖集 = 最小割

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

最小割的定义:把网络划分成两个集合,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

 

posted @ 2020-04-06 12:41  overrate_wsj  阅读(321)  评论(0编辑  收藏  举报