hdu 3081 二分 + 并查集 + 网络流

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


题意:有2n个小孩(n个男孩,n个女孩)在玩一个游戏。每一轮游戏女孩会挑选她喜欢的男生作为伴侣,并且规定她的女性朋友喜欢的男孩也是她喜欢并可以挑选的。而且她们遵循朋友的朋友也是朋友的原则。问最多能进行几轮游戏。


思路:

首先要得到每个女孩可以挑选的男孩范围,这个可以借助并查集来实现。将互为朋友的女生全部连在一起,视为一个头节点,然后这些女生喜欢的男生可以用一个vector记录在头节点下面。


然后建立一个超级源点与女生相连,建立一个超级汇点与男生相连,男女之间存在关系的建立容量为1的边。


如果进行了n轮游戏,合法的情况下最大流一定为 n * x。


然后跑一个dinic即可得出答案。



#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define rep(i, a, b)              for(int i(a); i <= (b); ++i)
#define dec(i, a, b)              for(int i(a); i >= (b); --i)
#define MP      				  make_pair

const int INF   =    1e9         +        7;
const int N     =    100000      +       10;
const int M     =    10000       +       10;
const int Q     =    1000        +       10;
const int A     =    300         +       10;

int G[A][A],father[A],girl[A*A],boy[A*A],dis[A];
vector<int> match[A];
int n,st,ed;

void init(void){
	rep(i,0,n){
		father[i] = i;
		match[i].clear();
	}
}

int find(int x){
	if(x != father[x])
		father[x] = find(father[x]);
	return father[x];
}

void build_G(int flow){
	memset(G,0,sizeof(G));
	
	rep(i,1,n){
		G[st][i] = flow;
		G[i+n][ed] = flow;
		
		int u = find(i);
		int len = match[u].size() - 1;
		rep(j,0,len){
			G[i][match[u][j]] = 1;
		}
	}	
}

void merge(int x,int y){
	x = find(x);
	y = find(y);
	if(x != y){
		father[y] = x;
	}
}

bool bfs(void){
	queue<int> que;
	memset(dis,0,sizeof(dis));
	
	dis[st] = 1;
	que.push(st);
	while(que.size()){
		int u = que.front();
		que.pop();
		
		rep(v,1,ed){
			if(G[u][v] && dis[v] == 0){
				dis[v] = dis[u] + 1;
				if(v == ed) return true;
				que.push(v);
			}
		}
	}
	return false;
}

int dfs(int u,int low){
	int uflow  = 0;
	if(u == ed) return low;
	
	rep(v,1,ed){
		if(dis[v] == dis[u] + 1 && G[u][v]){
			int flow = min(low-uflow, G[u][v]);
			flow = dfs(v,flow);
			
			G[u][v] -= flow;
			G[v][u] += flow;
			uflow += flow;
			
			if(uflow == low) break;
		}
	}
	
	if(uflow == 0) dis[u] = 0;
	return uflow;
}

int dinic(void){
	int ans = 0;
	while(bfs()){
		ans += dfs(st,INF);
	}
	
	return ans;
}

void solve(void){
	int left = 0,right = ed;
	int ans=0;
	
	while(left <= right){
		int mid = (left + right) >> 1;
		build_G(mid);
		
		int tem = dinic();
		if(tem == mid*n){
			left = mid + 1;
			ans = mid;
		}
		else
			right = mid - 1;
	}
	
	printf("%d\n",ans);
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int m,f;
		scanf("%d%d%d",&n,&m,&f);
		
		init();
		rep(i,1,m) scanf("%d%d",&girl[i],&boy[i]);
		
		rep(i,1,f){
			int x,y;
			scanf("%d%d",&x,&y);
			merge(x,y);
		}
		
		rep(i,1,m){
			int u = find(girl[i]);
			match[u].push_back(boy[i]+n);
		}
		
		st = 2*n + 1,ed = st + 1;
		solve();
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值