LA 3268 Jamie's Contact Groups 最大流

传送门:LA 3268 Jamie's Contact Groups

题目大意:有n(n <=  1000)个人和m(m <= 500)个组。一个人可能属于很多组。现在请你从某些组中去掉几个人,使得每个人只属于一个组,并使得人数最多的组中人员数目尽量少。

题目分析:

二分查找+最大流。

建立超级源汇,对每个人 i 建边(s,i,1),对每个组 j 建边(j + n,t,X(初始设为oo)),对每个人 i 所属的组 j ,建边(i,j + n,1)。

接下来二分到汇点的边的容量X(即组的最大容量)。如果最大流后满流,说明还可以减小组的最大容量,则减小容量继续搜,否则增大容量搜。由于左闭右开二分查找的性质最后 l 就是最终的答案。

代码如下:


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define clear(A, X) memset(A, X, sizeof A)
#define copy(A, B) memcpy(A, B, sizeof A)
using namespace std;

const int maxE = 1000000;
const int maxN = 10005;
const int maxM = 160;
const int maxQ = 1000000;
const int oo = 0x3f3f3f3f;

struct Edge{
	int v, c, n, rc;
}edge[maxE];//边组


int adj[maxN], cntE, cntEE;
int Q[maxQ], head, tail;//队列
int d[maxN], cur[maxN], pre[maxN], num[maxN];
int s, t, nv;//s:源点,t:汇点,nv:编号修改的上限
int n, m;
char str[maxE];

void addedge(int u, int v, int c){//添加边
	edge[cntE].v = v;
	edge[cntE].c = c;
	edge[cntE].rc = c;
	edge[cntE].n = adj[u];
	adj[u] = cntE++;
	
	edge[cntE].v = u;
	edge[cntE].c = 0;
	edge[cntE].rc = 0;
	edge[cntE].n = adj[v];
	adj[v] = cntE++;
}

void rev_bfs(){
	clear(num, 0);
	clear(d, -1);
	d[t] = 0;
	num[0] = 1;
	head = tail = 0;
	Q[tail++] = t;

	while(head != tail){
		int u = Q[head++];
		for(int i = adj[u]; ~i; i = edge[i].n){
			int v = edge[i].v;
			if(~d[v]) continue;
			d[v] = d[u] + 1;
			Q[tail++] = v;
			num[d[v]]++;
		}
	}
}

int ISAP(){
	copy(cur, adj);
	rev_bfs();
	int flow = 0, u = pre[s] = s, i;

	while(d[s] < nv){
		if(u == t){
			int f = oo, neck;
			for(i = s; i != t; i = edge[cur[i]].v){
				if(f > edge[cur[i]].c){
					f = edge[cur[i]].c;
					neck = i;
				}
			}
			for(i = s; i != t; i = edge[cur[i]].v){
				edge[cur[i]].c -= f;
				edge[cur[i] ^ 1].c += f;
			}
			flow += f;
			u = neck;
		}
		for(i = cur[u]; ~i; i = edge[i].n) if(d[edge[i].v] + 1 == d[u] && edge[i].c) break;
		if(~i){
			cur[u] = i;
			pre[edge[i].v] = u;
			u = edge[i].v;
		}
		else{
			if(0 == (--num[d[u]])) break;
			int mind = nv;
			for(i = adj[u]; ~i; i = edge[i].n){
				if(edge[i].c && mind > d[edge[i].v]){
					cur[u] = i;
					mind = d[edge[i].v];
				}
			}
			d[u] = mind + 1;
			num[d[u]]++;
			u = pre[u];
		}
	}
	
	return flow;
}

void init(){//初始化
	clear(adj, -1);
	cntE = 0;
}

int solve (int mid) {
	for (int i = 0; i < cntEE; ++i) edge[i].c = edge[i].rc;
	for (int i = cntEE; i < cntE; i += 2) edge[i].c = mid, edge[i ^ 1].c = 0;
	return ISAP() == n;
}

int search () {
	int l = 0, r = n + 1;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (solve(mid)) r = mid;
		else l = mid + 1;
	}
	return l;
}

void work(){
	init();
	s = n + m; t = n + m + 1; nv = t + 1;
	for (int i = 0; i < n; ++ i) {
		addedge (s, i, 1);
		fgets(str, maxE, stdin);
		int x = 0, flag = 0;
		for (int j = 0; str[j]; ++ j) {
			if (str[j] < '0' || str[j] > '9') {
				if (flag) addedge (i, x + n, 1);
				x = flag = 0;
			}
			else {
				x = x * 10 + str[j] - '0';
				flag = 1;
			}
		}
	}
	cntEE = cntE;
	for (int i = 0; i < m; ++ i) addedge (i + n, t, oo);
	printf("%d\n", search());
}

int main(){
	while(~scanf("%d%d%*c", &n, &m) && (n || m)) work();
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值