网络最大流SAP算法

#include<cstdio>//拆点 将1~n的女生每个拆成两个分别为n+1到2*n,坏女生连的是和自己关系不好的男生
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF=0x7fffffff;
int f[100010],n,m,p,k,cont,arr[1010][1010],dis[100010],gap[100010],first[100010];
int cur[100010];//当前弧
int exc[100010];//该点盈余
int pre[100010];//前驱节点
struct node
{
    int u,v,w;
    int next;
} x[100010];
struct zp
{
    int a,b;
} edge[100010];
int finds(int a)
{
    int b=a;
    while(a!=f[a])
        a=f[a];
    while(b!=a)
    {
        int tmp;
        tmp=f[b];
        f[b]=a;
        b=tmp;
    }
    return a;
}
int merger(int a,int b)
{
    int fa=finds(a),fb=finds(b);
    if(fa!=fb)
        f[fa]=fb;
}
void add_edge(int a,int b,int value)
{
    x[cont].u=a,x[cont].v=b,x[cont].w=value;
    x[cont].next=first[a],first[a]=cont++;
    x[cont].u=b,x[cont].v=a,x[cont].w=0;
    x[cont].next=first[b],first[b]=cont++;
}
void build(int flow)//建图
{
    cont=0;
    memset(first,-1,sizeof(first));
    memset(arr,0,sizeof(arr));
    for(int i=0; i<m; i++)
    {
        int a=edge[i].a,b=edge[i].b;
        arr[finds(a)][b]=1;//用老大来和自己的朋友建立关系
    }
    for(int i=1; i<=n; i++)
    {
        add_edge(2*n+i,3*n+1,flow);//男生向汇点建边
        add_edge(0,i,flow);//源点向女生建边
        add_edge(i,i+n,k);//好女生向坏女生建边
        for(int j=1; j<=n; j++)
            if(arr[finds(i)][j]) add_edge(i,2*n+j,1);//如果老大和该男生是朋友则自己也和该男生是朋友
            else add_edge(i+n,2*n+j,1);//否则不是朋友
    }
}
int SAP(int s,int e,int N)//源点,汇点,总点数
{
    int Max_flow=0,u=s;
    pre[s]=-1;
    exc[s]=INF;
    memset(gap,0,sizeof(gap));//标号是i的点的个数
    memset(dis,0,sizeof(dis));//距离标号
    gap[0]=N;
    for(int i=0; i<=N; i++) //初始化当前弧
        cur[i]=first[i];
    while(dis[s]<N)
    {
        int flag=1;
        if(u==e)//找到汇点
        {
            Max_flow+=exc[e];//更新最大流
            for(int i=pre[e]; i!=-1; i=pre[i]) //更新当前允许路
            {
                int id=cur[i];
                x[id].w-=exc[e];
                x[id^1].w+=exc[e];
                exc[i]-=exc[e];
                if(x[id].w==0) u=i;//退回到容量为0的弧尾
            }
        }
        for(int i=cur[u]; i!=-1; i=x[i].next) //从当前弧开始寻找允许弧
        {
            int v=x[i].v;
            if(x[i].w>0&&dis[u]==dis[v]+1)//找到允许弧
            {
                flag=0;//找到允许弧
                cur[u]=i;//更新当前弧
                pre[v]=u;//记录前驱
                exc[v]=min(exc[u],x[i].w);//计算最大增广
                u=v;
                break;
            }
        }
        if(flag)//没有找到允许弧
        {
            if(--gap[dis[u]]==0) break;//出现断层结束
            int minn=N;
            cur[u]=first[u];
            for(int i=first[u]; i!=-1; i=x[i].next) //找离当前点可到达的最小层次
            {
                int v=x[i].v;
                if(x[i].w>0&&dis[v]<minn)
                {
                    minn=dis[v];
                    cur[u]=i;//修改当前弧标记
                }
            }
            dis[u]=minn+1;//更新该点层次
            gap[dis[u]]++;//层次点数++
            if(u!=s) u=pre[u];//回溯继续寻找允许弧
        }
    }
    return Max_flow;
}
int main()
{
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        scanf("%d%d%d%d",&n,&m,&k,&p);
        for(int i=0; i<=n; i++)
            f[i]=i;
        for(int i=0; i<m; i++)
            scanf("%d%d",&edge[i].a,&edge[i].b);
        for(int i=0; i<p; i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            merger(a,b);
        }
        for(int i=1; i<=n; i++)
            f[i]=finds(i);
        int L=0,R=n,ans=0;
        while(L<=R)//二分求解
        {
            int mid=(L+R)>>1;
            build(mid);
            if(SAP(0,3*n+1,3*n+2)==mid*n)
            {
                L=mid+1;
                ans=mid;
            }
            else
                R=mid-1;
        }
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值