hdu3081——最大流(二分图)+并查集(floyd)+二分

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3081

Presumably, you all have known the question of stable marriage match. A girl will choose a boy; it is similar as the game of playing house we used to play when we are kids. What a happy time as so many friends playing together. And it is normal that a fight or a quarrel breaks out, but we will still play together after that, because we are kids.
Now, there are 2n kids, n boys numbered from 1 to n, and n girls numbered from 1 to n. you know, ladies first. So, every girl can choose a boy first, with whom she has not quarreled, to make up a family. Besides, the girl X can also choose boy Z to be her boyfriend when her friend, girl Y has not quarreled with him. Furthermore, the friendship is mutual, which means a and c are friends provided that a and b are friends and b and c are friend.
Once every girl finds their boyfriends they will start a new round of this game—marriage match. At the end of each round, every girl will start to find a new boyfriend, who she has not chosen before. So the game goes on and on.
Now, here is the question for you, how many rounds can these 2n kids totally play this game?

Input

There are several test cases. First is a integer T, means the number of test cases.
Each test case starts with three integer n, m and f in a line (3<=n<=100,0<m<n*n,0<=f<n). n means there are 2*n children, n girls(number from 1 to n) and n boys(number from 1 to n).
Then m lines follow. Each line contains two numbers a and b, means girl a and boy b had never quarreled with each other.
Then f lines follow. Each line contains two numbers c and d, means girl c and girl d are good friends.

Output

For each case, output a number in one line. The maximal number of Marriage Match the children can play.

Sample Input

1
4 5 2
1 1
2 3
3 2
4 2
4 4
1 4
2 3

Sample Output

2

大概你们都知道婚姻稳定的问题。女孩会选择男孩;它和我们小时候玩的玩房子的游戏很相似。这么多朋友一起玩,多么快乐的时光。打架或争吵是正常的,但在那之后我们还是会一起玩,因为我们还是孩子。‎
‎现在,有2个孩子,n个男孩编号从1到n,和n女孩编号从1n。你知道,女士们第一。所以,每个女孩都可以先选择一个男孩,她没有争吵过,组成一个家庭。此外,女孩X也可以选择男孩Z作为她的男朋友时,她的朋友,女孩Y没有和他吵架。此外,友谊是相互的,这意味着a和c是朋友,只要a和b是朋友,b和c是朋友。‎
‎一旦每个女孩找到他们的男朋友,他们将开始新一轮的这个游戏-婚姻比赛。在每轮结束时,每个女孩都会开始寻找一个新的男朋友,她以前没有选择过。因此,游戏继续。‎
‎现在,这是你要问的问题,这2n个孩子能玩多少回合这个游戏?‎

‎输入‎

‎有几个测试用例。首先是整数 T,表示测试用例的数量。‎
‎每个测试用例从一行中的三个整数 n、m 和 f 开始(3<\n\lt;#100,0_lt;m_lt;n\n,0_lt;_f_lt;n)。n 表示有 2\n 儿童、n 个女孩(从 1 到 n 的数字)和 n 个男孩(从 1 到 n 的数字)。‎
‎然后,m 行跟随。每行包含两个数字 a 和 b,表示女孩 a 和男孩 b 从未争吵过。‎
‎然后f行跟随。每行包含两个数字 c 和 d,表示女孩 c 和女孩 d 是好朋友。‎

‎输出‎

‎对于每种情况下,在一行中输出一个数字。孩子可以玩的最大数量的婚姻匹配。

 

这个题应该是比较好的一个网络流的题,多种方法都可以实现,最大流/二分匹配+并查集/Floyd传递闭包都可以处理。

我这里给出基于最大流和并查集的实现,因为我对这方面不是很熟悉,所以带多用一下。

建模的话很好处理,最重要的是之后的女生是朋友问题的处理,其实就是判断一个女生和另一个女生是不是朋友。

基于并查集的实现就是如果发现一个女孩和另一个女孩是好朋友,那么就合并,之后每次枚举每个女生,如果发现两个女生的根节点相同(是好朋友),那么她们都建边到双方的男朋友。

之后就是最大流的知识,首先我们需要知道最大流应该是最大的回合数和n个儿童的乘积。

然后二分枚举这个最大的回合数就行了。

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<vector>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn= 2020+5;
struct Edge
{
    int from,to,cap,flow;
    Edge(){}
    Edge(int f,int t,int c,int flow):from(f),to(t),cap(c),flow(flow){}
};
struct Dinic
{
    int n,m,s,t;
    vector<Edge> edges;
    vector<int> G[maxn];
    int d[maxn];
    bool vis[maxn];
    int cur[maxn];
    void init(int n,int s,int t)
    {
        this->n=n, this->s=s, this->t=t;
        edges.clear();
        for(int i=0;i<n;i++) G[i].clear();
    }
    void AddEdge(int from,int to,int cap)
    {
        edges.push_back( Edge(from,to,cap,0) );
        edges.push_back( Edge(to,from,0,0) );
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    bool BFS()
    {
        queue<int> Q;
        memset(vis,0,sizeof(vis));
        vis[s]=true;
        d[s]=0;
        Q.push(s);
        while(!Q.empty())
        {
            int x= Q.front(); Q.pop();
            for(int i=0;i<G[x].size();++i)
            {
                Edge& e=edges[G[x][i]];
                if(!vis[e.to] && e.cap>e.flow)
                {
                    vis[e.to]=true;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int x,int a)
    {
        if(x==t || a==0) return a;
        int flow=0,f;
        for(int& i=cur[x]; i<G[x].size();++i)
        {
            Edge& e=edges[G[x][i]];
            if(d[e.to]==d[x]+1 && (f=DFS(e.to,min(a,e.cap-e.flow) ) )>0)
            {
                e.flow +=f;
                edges[G[x][i]^1].flow -=f;
                flow +=f;
                a-=f;
                if(a==0) break;
            }
        }
        return flow;
    }
    int max_flow()
    {
        int ans=0;
        while(BFS())
        {
            memset(cur,0,sizeof(cur));
            ans +=DFS(s,INF);
        }
        return ans;
    }
}DC;
int G[maxn][maxn],p[maxn];
int find(int x){
	return p[x]==-1?x:p[x]=find(p[x]);
}
void unit(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
		p[fx]=fy;
}
bool judge(int n,int lim){
	int s=0,t=2*n+1;
	DC.init(n*2+2,s,t);
	for(int i = 1;i<=n;++i)
		for(int j = n+1;j<=2*n;++j)
			if(G[i][j])
				DC.AddEdge(i,j,1);
	for(int i = 1;i<=n;++i)
		DC.AddEdge(s,i,lim);
	for(int i = n+1;i<=n*2;++i)
		DC.AddEdge(i,t,lim);
	if(DC.max_flow()==n*lim) return 1;
	else return 0;
}
int main(int argc, char** argv) {
	int T;
	scanf("%d",&T);
	while(T--){
		int n,m,f;
		memset(p,-1,sizeof(p));
		memset(G,0,sizeof(G));
		scanf("%d%d%d",&n,&m,&f);
		while(m--){
			int x,y;
			scanf("%d%d",&x,&y);
			G[x][y+n]=1; 
		}
		while(f--){
			int x,y;
			scanf("%d%d",&x,&y);
			unit(x,y);
		}
		for(int i = 1;i<=n;++i){
			for(int j = i+1;j<=n;++j){
				if(find(i)==find(j)){
					for(int k = n+1;k<=n*2;++k){
						if(G[i][k]||G[j][k])
							G[i][k]=G[j][k]=1; 
					}
				}
			}
		}
		int l=0,r=100;
		while(l<r){
			int mid=l+(r-l+1)/2;//坑点,如果变成(l+r)/2 就会超时,真玄学 
			if(judge(n,mid)) l=mid;
			else r=mid-1;
		}
		printf("%d\n",l);
	} 
	return 0;
}

最后抛一个网上大佬的基于二分匹配和Floyd的实现,其实这才是最简单的!!!

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 305;
int check[N],match[N];
int n,m,f,T;
int g[N][N];
int dfs(int s){
    for(int i=n+1;i<=2*n;i++){
        if(!g[s][i] || check[i]) continue ;
        check[i]=1;
        if(match[i]==-1 || dfs(match[i])){
            match[i]=s;
            return 1;
        }
    }
    return 0;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m,&f);
        memset(g,0,sizeof(g));
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            g[u][v+n]=1;
        }
        for(int i=1;i<=f;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            g[u][v]=g[v][u]=1;
        }
        for(int k=1;k<=n;k++)
            for(int i=1;i<=n;i++)
                for(int j=1;j<=2*n;j++)
                    g[i][j]=(g[i][k]&&g[k][j])||g[i][j];
        int ans = 0,flag=0;
        while(1){
            flag = 0;
            memset(match,-1,sizeof(match));
            for(int i=1;i<=n;i++){
                memset(check,0,sizeof(check));
                if(!dfs(i)){
                    flag=1;
                    break ;
                }
            }
            if(flag) break;
            for(int i=n+1;i<=2*n;i++)
                g[match[i]][i]=0;
            ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值