传送门: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;
}