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