最大流

问题描述:需要将一批物品从节点s(源点)运送到t(成为汇点),可以从其他点中转,但是有向图的每个边上都有一个权值,表示这条边上该方向最多能够运送的物品数量
超出这些量以后这条边就变得不可用(相当于没有这条边)
求从s到t能够运送通过的最大物品数量。

解决:增广路算法

 

模板:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<queue>
#define N 55
using namespace std;

//a数组保存到每个点的最小残量,p数组保存每一条增广路的路径,cap保存各个点之间的容量
//flow保存各个点之间的残量 
int n,m,cap[N][N],flow[N][N],a[N],p[N];
int maxflow(int s,int t) {
    queue<int>q;
    int f=0,u,v;//f中保存的是最大流 
    memset(flow,0,sizeof(flow));
    while(1) {
        memset(a,0,sizeof(a));
        a[s]=0x7FFFFFFF;//之所以设为最大值,一是标记访问过了,二是避免比别的残量小,从而影响找到真正的最小残量 
        q.push(s);
        while(!q.empty()) {
            u=q.front(),q.pop();
            
            for(v=1;v<=n;v++) {
                if(!a[v]&&cap[u][v]>flow[u][v]) {//相通并且有流量,在这里a[i]=0表示这个点还没访问过,a[i]!=0的时候表示残量 
                    p[v]=u;//记录下当前结点的父节点 
                    q.push(v);//当前结点入队 
                    a[v]=a[u]<cap[u][v]-flow[u][v]?a[u]:cap[u][v]-flow[u][v];//找到到当前结点的最小残量 
                }
            }
        }
        if(a[t]==0) break;//最小残量为0,即不再有增广路,退出 
        for(u=t;u!=s;u=p[u]) {
            flow[p[u]][u]=+a[t];//更新流量,正反两个方向 
            flow[u][p[u]]-=a[t];
            printf("%d <- ",u);
        }
        printf("%d  %d\n",s,a[t]);
        f+=a[t];
    }
    return f;
}
int main() {
    int T,i,j,a,b,w,f,s,t;
    scanf("%d",&T);
    while(T--) {
        scanf("%d %d %d %d",&n,&m,&s,&t);
        for(i=1;i<=n;i++)//初始化路径 
            for(j=1;j<=n;j++)
                cap[i][j]=cap[j][i]=0;//没有容量表示不通 
        for(i=1;i<=m;i++){
            scanf("%d %d %d",&a,&b,&w);//保存刚刚输入的路的起始和终点,便于后面更新路径
            cap[a][b]=w;
        }
        f=maxflow(s,t);
        printf("%d\n",f);
    }
    system("pause");
    return 0;
}

/*
6 10 1 6
1 2 16
1 3 13
2 3 10
3 2 4
3 5 14
2 4 12
5 4 7
4 3 9
5 6 4
4 6 20


23

*/


这是一个基本的最大流问题,除了处理最大流问题,经过简单的变形,可以用来求二分图的最大匹配问题。如下

描述

月老准备给n个女孩与n个男孩牵红线,成就一对对美好的姻缘。

现在,由于一些原因,部分男孩与女孩可能结成幸福的一家,部分可能不会结成幸福的家庭。

现在已知哪些男孩与哪些女孩如果结婚的话,可以结成幸福的家庭,月老准备促成尽可能多的幸福家庭,请你帮他找出最多可能促成的幸福家庭数量吧。

假设男孩们分别编号为1~n,女孩们也分别编号为1~n。

输入
第一行是一个整数T,表示测试数据的组数(1<=T<=400)
每组测试数据的第一行有两个整数n,K,其中男孩的人数与女孩的人数都是n。(n<=500,K<=10 000)
随后的K行,每行有两个整数i,j表示第i个男孩与第j个女孩有可能结成幸福的家庭。(1<=i,j<=n)
输出
对每组测试数据,输出最多可能促成的幸福家庭数量
样例输入
1
3 4
1 1
1 3
2 2
3 2
样例输出
2

分析:我们增加一个源点和一个终点,使源点到左边集合所有点、右边集合所有点到终点都增加一条边,且令途中多有边流量为1,则求源点到终点的最大流就是二分图的最大匹配!(对于这道题来说,可能会超时,但是写法经过优化之后是可以过的!)

如图所示:

 

代码实现:

#include<stdio.h>
#include<string.h>
#include<queue>
#define N 1100
using namespace std;

int flow[N][N],cap[N][N],a[N],p[N];

int maxflow(int s, int t){
    queue<int>q;
    int f=0,u,v;
    //memset(flow,0,sizeof(flow));
    for(u=0;u<=t;u++)
        for(v=0;v<=t;v++)
            flow[u][v]=0; 
    while(1) {
        memset(a,0,sizeof(a));
        a[s]=0x7FFFFFFF;
        q.push(s);
        while(!q.empty()) {
            u=q.front(),q.pop();
            for(v=0;v<=t;v++) {
                if(!a[v]&&flow[u][v]<cap[u][v]){
                    p[v]=u;
                    q.push(v);
                    a[v]=a[u]<cap[u][v]-flow[u][v]?a[u]:cap[u][v]-flow[u][v];
                }
            }
        }
        if(a[t]==0) break;
        
        for(u=t;u!=s;u=p[u]){
            flow[p[u]][u]=+a[t];//更新流量,正反两个方向 
            flow[u][p[u]]-=a[t];
        }
        f+=a[t];
    }
    return f;
}

int main() {
    int i,j,T,n,k,x,y,ans;
    scanf("%d",&T);
    while(T--) {
        scanf("%d %d",&n,&k);
        //memset(cap,0,sizeof(cap));
        for(i=0;i<2*n+2;i++)
            for(j=0;j<2*n+2;j++)
                cap[i][j]=0; 
        for(i=1;i<=k;i++){
            scanf("%d %d",&x,&y);
            cap[x][n+y]=1;
        }
        for(i=1;i<=n;i++){//构建源点到左边集合的边 
            cap[0][i]=1;
        }
        for(i=n+1;i<=2*n;i++){//构建右边的边到终点的边 
            cap[i][2*n+1]=1;
        }
        n=2*n+2;
        ans=maxflow(0,n-1);
        printf("%d\n",ans);
    }
    system("pause");
    return 0;
}


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值