POJ 2289 Jamie's Contact Groups 多重匹配+二分

题目大意是给你一堆联系人, 每个人有几个标签, 然后让你将他们分组(每组里的人要标签相同), 问其中人最多的组的人数最少是多少.

有点像鸽巢的感觉...开始我想了一个, 以为是每次求最大匹配, 然后把已盖点的邻边全删掉, 然后看能求几次最大匹配....WA之...

其实显然是错的, 因为每次删去一堆边你不能保证这样的匹配是最优的.

然后发现有多重匹配这种东西....其实也就是给定{V}最多能匹配多少个u, 然后求最大匹配.

用 flag[v][k] 表示点v匹配到第k个点. >=0表示匹配的点是u =  flag[v][k]. <0表示还未匹配到第k点.

对已经匹配满k个点的情况, dfs里要枚举v匹配的这k个u, 然后看有没有增广路, 有的话让出匹配.

这样, 二分答案就能求出最少是多少, 二分条件是 最大匹配是否等于人数(表示这样的限度是否足够容纳所有人).


貌似也可以用最大流+二分做.但是肯定是匹配好写点


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
inline int Rint() { int x; scanf("%d", &x); return x; }
inline int max(int x, int y) { return (x>y)? x: y; }
inline int min(int x, int y) { return (x<y)? x: y; }
#define FOR(i, a, b) for(int i=(a); i<=(b); i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(x) for(int i=0; i<(x); i++)
typedef long long int64;
#define INF (1<<30)
#define bug(s) cout<<#s<<"="<<s<<" "

//	1/ 多重匹配, v能匹配多个u.
//	2/ 指定每个v的匹配上限k, 若最大匹配=u. 则说明k足够大. 从而可以二分答案.

#define MAXN 1002
#define MAXM 502
#define MAXE MAXN*MAXM	//最大边数
struct node
{
	int u, v, w;
}a[MAXE];
int fa[MAXN], next[MAXE], idx;
int flag[MAXM][MAXN];
int tot[MAXM];	//tot[v] 表示v已经匹配的u数目
int vis[MAXM];	//dfs访问标记, 只用vis[v]. u是枚举一遍的.
int n, m;

void addedge(int u, int v, int w) { a[idx].u=u; a[idx].v=v; a[idx].w=w; next[idx]=fa[u]; fa[u]=idx++; }

int dfs(int u, int k)	//匹配k
{
	for(int e = fa[u]; e!=-1; e=next[e])
	{
		int v = a[e].v;
		if(vis[v]) continue;
		vis[v] = 1;
		if(tot[v]<k)	//tot[v]<k, 未满
		{
			flag[v][tot[v]++] = u;
			return 1;
		}
		FOR(i, 0, k-1)	//tot[v]=k,  满
			if(dfs(flag[v][i], k))
			{
				flag[v][i] = u;
				return 1;
			}
	}
	return 0;
}

void print()
{
	FOR(i, 1, m)
	{
		bug(i);bug(tot[i])<<endl;
	}
}

int hungry(int k)
{
	int ans = 0;
	memset(flag, 0, sizeof(flag));
	memset(tot, 0, sizeof(tot));
	FOR(u, 1, n)
	{
		memset(vis, 0, sizeof(vis));
		if(dfs(u, k))
			ans++;
	}
	//bug(k)<<endl;
	//print();
	return ans;
}

int solve()
{
	int l = 1, r = n;	//每组几个人
	while(l<r)	//当 l==r跳出
	{
		int mid = (l+r)>>1;
		if(hungry(mid) == n)
			r = mid;
		else
			l = mid+1;
	}
	return l;
}

int main()
{
	while(scanf("%d%d", &n, &m) == 2)
	{
		if(!n && !m) break;
		idx = 0;
		memset(fa, -1, sizeof(fa));
		FOR(i, 1, n)
		{
			//bug(i);
			char buf[20];
			scanf("%s", buf);
			int v;
			char ch;
			while(scanf("%d%c", &v, &ch) == 2)
			{
				//bug(v);bug(ch)<<endl;
				v++;	//1-th
				addedge(i, v, 1);
				if(ch == '\n') break;
			}
		}
		printf("%d\n", solve());
	}
}


转载于:https://www.cnblogs.com/tclh123/archive/2012/08/05/6171742.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值