网络流建图——并查集的应用(HDU - 3081)

题目概述

 有N个女孩要与N个男孩玩配对游戏.每个女孩有一个可选男孩的集合(即该女孩可以选自己集合中的任意一个男孩作为该轮的搭档).

然后从第一轮开始,每个女孩都要和一个不同的男孩配对.如果第一轮N个女孩都配对成功,那么就开始第二轮配对,女孩依然从自己的备选男孩集合中选择,但是不能选那些已经被该女孩在前几轮选择中选过的男孩了(比如i女孩在第一轮选了j男孩,那么i在第二轮就不能选j男孩了). 问你游戏最多能进行多少轮?

问题分析:

 不否认这个题可以用二分图匹配去解决。但这里不做探究。以下为网络流建模解法:

 这里如果要使用网络流求解的话,显然我们发现,同一个好友圈(juan)的女生具有相同的性质,即互为好友的女生在这个问题中是等价的,即可以通过一种变换,使得女生A变成女生B~~那么我们通过使用并查集,可以实现将A与B的合并,此时同一并查集中的女生都链接同一组男性。显然这是可行的.

并查集将女生连成整体后,再从女生流到男生(一 一 对 应 所以权值为1)

二分枚举局数(原点到女生的路宽与男生到汇点的路宽)

流入全部流出即可以完成这这样的局数。

INF以介绍过,这里仅提供并查集算法

#include<stdio.h>  
#include<string.h>  
#include<queue>  
#include<algorithm>  
using namespace std;  
typedef int captype;//把存储的数据类型在这里定义,避免了大量更改操作
const int MAXN = 210;   //点的总数  
const int MAXM = 40010;    //边的总数  
const int INF = 1<<30;  
struct EDG{  
    int to,next;  
    captype cap,flow;  
} edg[MAXM];  
int eid,head[MAXN];  
int gap[MAXN];  //每种距离(或可认为是高度)点的个数  
int dis[MAXN];  //每个点到终点eNode 的最短距离  
int cur[MAXN];  //cur[u] 表示从u点出发可流经 cur[u] 号边  
int pre[MAXN];  
  
void init(){  
    eid=0;  
    memset(head,-1,sizeof(head));  
}  
//有向边 三个参数,无向边4个参数  
void addEdg(int u,int v,captype c,captype rc=0){  
    edg[eid].to=v; edg[eid].next=head[u];  
    edg[eid].cap=c; edg[eid].flow=0; head[u]=eid++;  
  
    edg[eid].to=u; edg[eid].next=head[v];  
    edg[eid].cap=rc; edg[eid].flow=0; head[v]=eid++;  
}  
captype maxFlow_sap(int sNode,int eNode , int n){  
    memset(gap,0,sizeof(gap));  
    memset(dis,0,sizeof(dis));  
    memcpy(cur,head,sizeof(head));  
    pre[sNode]=-1;  
    gap[0]=n;  
  
    captype ans = 0;  
    int u=sNode;  
    while(dis[sNode]<n){  
        if(u==eNode){  
            captype mint = INF , mincap = INF;  
            int minid ;  
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])  
            if(mint > edg[i].cap - edg[i].flow){  
                mint = edg[i].cap - edg[i].flow ;  
                minid=i;  
            }  
            for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){  
                edg[i].flow += mint;  
                edg[i^1].flow -=mint;  
            }  
            ans += mint;  
            u = edg[minid^1].to;  
            continue;  
        }  
        bool flag=false;  
        for(int i=cur[u]; i!=-1; i=edg[i].next)  
        if(edg[i].cap-edg[i].flow>0 && dis[u]==dis[edg[i].to]+1){  
            cur[u]=pre[edg[i].to]=i;  
            flag=true;  
            break;  
        }  
        if(flag){  
            u=edg[cur[u]].to;  
            continue;  
        }  
        int minh=n;  
        for(int i=head[u]; i!=-1; i=edg[i].next)  
            if(edg[i].cap-edg[i].flow>0 && minh>dis[edg[i].to]){  
                minh=dis[edg[i].to];  
                cur[u]=i;  
            }  
        gap[dis[u]]--;  
        if(gap[dis[u]]==0)  
            return ans;  
        dis[u]=minh+1;  
        gap[dis[u]]++;  
        if(u!=sNode)  
            u=edg[pre[u]^1].to;  
    }  
    return ans;  
}  
void changCap(int s,int t,int k){  
    for(int i=head[s]; i!=-1; i=edg[i].next)  
        edg[i].cap=k;  
    for(int i=head[t]; i!=-1; i=edg[i].next)  
        edg[i^1].cap=k;  
    for(int i=0; i<eid; i++)//每一次都要清0  
        edg[i].flow=0;  
}  
int father[MAXN];  
int findfath(int x){  
    if(x!=father[x])  
        father[x]=findfath(father[x]);  
    return father[x];  
}  
void link(int x,int y){  
    x=findfath(x);  
    y=findfath(y);  
    father[x]=y;  
}  
int mapt[105][105];  
void changMap(int n){  
    int tmp[105][105]={0};  
    for(int i=1; i<=n; i++) //对于在同一棵树上可以用一个点来替换(根节点)  
        father[i]=findfath(i);  
    for(int i=1; i<=n; i++){  
        int ti=father[i];  
        for(int j=1; j<=n; j++)  
            tmp[ti][j]|=mapt[i][j];  
    }  
    for(int i=1; i<=n; i++){  
        int ti=father[i];  
        for(int j=1; j<=n; j++)  
            mapt[i][j]=tmp[ti][j];  
    }  
}  
int main(){  
    int T;  
    int n,m,f;  
    int a,b;  
    scanf("%d",&T);  
    while(T--){  
        scanf("%d%d%d",&n,&m,&f);  
        memset(mapt,0,sizeof(mapt));  
        while(m--){  
            scanf("%d%d",&a,&b);  
            mapt[a][b]=1;  
        }  
        for(int i=1; i<=n; i++)  
            father[i]=i;  
        while(f--){  
            scanf("%d%d",&a,&b);  
            link(a,b);  
        }  
          
        changMap(n);  
        init();  
        int s=0 , t=2*n+1;  
        //女用1~n点表示,男用n+1~n+n表示  
        for(int i=1; i<=n; i++){  
            addEdg(s,i,0);  
            addEdg(i+n,t,0);  
            for(int j=1; j<=n; j++)  
            if(mapt[i][j])  
                addEdg(i,j+n,1);  
        }  
        int l=0,r=n ,ans=0;  
        while(l<=r){  
            m=(l+r)>>1;  
            changCap(s,t,m);  
            int maxflow=maxFlow_sap(s,t,t+1);  
            if(maxflow==n*m)  
                ans=m , l=m+1;  
            else r=m-1;  
        }  
        printf("%d\n",ans);  
    }  
}  

猜你喜欢

转载自blog.csdn.net/qq_41104612/article/details/80663619