【hdu3081】【二分法】【最大流】【并查集】Marriage Match II

16 篇文章 0 订阅

稍微综合一点的题目。

建图方法:

对每个女孩喜欢的男孩,连一条容量为1的边

若两个女孩a,b是朋友,则对于a连了边的男孩,b也要连边,使用并查集维护

然后二分出游戏进行的回合数p,s -> 每个女孩,容量为p每个男孩 -> t,容量为p

最大流 = n * p说明p是可行的,这样就可以得出最多进行的回合数了。

代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 200 + 10;
const int maxm = maxn * maxn;
const int inf = 0x3f3f3f3f;
struct node
{
	int a,b;
}pair[maxm];
int cap[maxn][maxn],pre[maxn],cur[maxn],dis[maxn],gap[maxn];
int f[maxn];
int T,n,m,h;
int s,t,nodenum;

inline int min(int a,int b)
{
	return a < b ? a : b;
}

void set_init()
{
	for(int i = 1;i <= n;i++)f[i] = i;
}

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

bool query(int a,int b)
{
	return find(a) == find(b);
}
void set_union(int a,int b)
{
	f[find(a)] = find(b);
}

void init()
{
	freopen("hdu3081.in","r",stdin);
	freopen("hdu3081.out","w",stdout);
}

void build_map()
{
	memset(cap,0,sizeof(cap));
	for(int i = 1;i <= m;i++)
	{
		cap[pair[i].a][pair[i].b + n] = 1;
	}
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= n;j++)
		{
			if(i != j && query(i,j))
			{
				for(int k = 1;k <= n;k++)
				{
					if(cap[i][k+n] && !cap[j][k+n])cap[j][k+n] = 1;
					if(cap[j][k+n] && !cap[i][k+n])cap[i][k+n] = 1;
				}
			}
		}
	}
}

int sap()
{
	memset(gap,0,sizeof(gap));
	memset(cur,0,sizeof(cur));
	memset(dis,0,sizeof(dis));
	int u = pre[s] = s,maxflow = 0,aug = inf;
	gap[s] = nodenum;
	while(dis[s] < nodenum)
	{
loop:   for(int v = cur[u];v < nodenum;v++)
		{
			if(cap[u][v] && dis[u] == dis[v] + 1)
			{
				aug = min(aug,cap[u][v]);
				cur[u] = v;
				pre[v] = u;
				u = v;
				if(v == t)
				{
					maxflow += aug;
					for(u = pre[u];v != s;v = u,u = pre[u])
					{
						cap[u][v] -= aug;
						cap[v][u] += aug;
					}
					aug = inf;
				}
				goto loop;
			}
		}
		int mind = nodenum;
		for(int v = 1;v < nodenum;v++)
		{
			if(cap[u][v] && (mind > dis[v]))
			{
				cur[u] = v;
				mind = dis[v];
			}

		}
		if((--gap[dis[u]]) == 0)break;
		gap[dis[u] = mind + 1]++;
		u = pre[u];
	}
	return maxflow;
}

bool check(int p)
{
	build_map();
	for(int i = 1;i <= n;i++)cap[s][i] = p;
	for(int i = n + 1;i <= 2 * n;i++)cap[i][t] = p;
	return sap() == (p * n);
}

void solve()
{
	s = 0,t = 2 * n + 1;
	nodenum = t + 1;
	int l = 0,r = n;
	while(l < r)
	{
		int m = (l + r + 1) >> 1;
		if(check(m))l = m;
		else r = m - 1;
	}
	printf("%d\n",l);
}

void readdata()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(cap,0,sizeof(cap));
		scanf("%d%d%d",&n,&m,&h);
		set_init();
		for(int i = 1;i <= m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			pair[i].a = a;pair[i].b = b;
		}
		for(int i = 1;i <= h;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			if(!query(a,b))set_union(a,b);
		}
		solve();
	}
}

int main()
{
	init();
	readdata();
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值