有一群牛,若干饮料和食物。每只牛只能选择一中套餐:一种饮料和一种食物。被选中的食物和饮料不能 再被其他牛选用。问最多能让多少只牛选到自己中意的套餐。 建立一个源点跟每种食物相连,建立一个汇点跟每种饮料连边,将牛拆点分为A牛B牛,第i只牛中意的食 物和饮料分别跟A牛和B牛连边(注意边的方向)。A和B牛之间连边。所有边权均为1。做一次SAP即可: 擦,邻接表的模板又挂了。 #include <pzjay_cpyr> const int sup = 810; const int INF = 1 << 25; int cap[sup][sup];//容量 int pre[sup], d[sup];//i的前驱节点和i到t的距离d函数 int cnt[sup];//统计数组 int n, N; int s, t; inline int Min(int a, int b) { if(a < b) return a; return b; } void init(int s, int t)//初始化函数,bfs求层次图 { queue <int> Q; memset(d, 1, sizeof(d));//初始距离无限大 memset(cnt, 0, sizeof(cnt)); d[t] = 0;//自己到自己为0 cnt[0] = 1;//0值出现一次 Q.push(t);//汇点入队列,以此为起点BFS构造d函数 while(!Q.empty())//bfs { int u = Q.front(); Q.pop(); for(int v = 0; v <= t; ++v) if(d[v] > t && cap[v][u] > 0)//假如未被标记(d函数最大值才为n+1) ,并且可以被标记 { d[v] = d[u] + 1;//倒着回溯标记的 Q.push(v); ++cnt[d[v]]; } } } int find(int u)//寻找以u为起点的允许弧 { for(int v = 0; v <= t; ++v) if(cap[u][v] > 0 && d[u] == d[v] + 1) return v; return -1; } int relable(int u)//对点u重标记:将u抬升可作为可用弧的起点 { int MIN = INF; for(int v = 0; v <= t; ++v) if(cap[u][v] > 0) MIN = Min(MIN, d[v] + 1); if(INF == MIN)//重标记失败,那么将点u重标记为n,表示弃用(不再有增广路从这点经过) return t;//这里弃用的点被重标记为多少,直接关系到下面P_R算法while退出的条 件 return MIN; } int P_R(int s, int t)//push and relable算法 { int max_flow = 0; int u = s; memset(pre, -1, sizeof(pre)); while(d[s] < t)//d函数是递增的,所以当超过n+1时,可以退出了 { int v = find(u); if(v >= 0)//找到了 { pre[v] = u; u = v; if(u == t)//到达汇点,开始增广 { int add = INF;//增广量 for(int k = t; k != s; k = pre[k]) add = Min(add, cap[pre[k]][k]);//这里cap既是原网 络图,又是残留图 for(int k = t; k != s; k = pre[k]) { cap[pre[k]][k] -= add; cap[k][pre[k]] += add; } max_flow += add; u = s; } } else//没找到,进行重标记 { int tp = relable(u);//u的新d值为tp ++cnt[tp]; --cnt[d[u]]; if(0 == cnt[d[u]])//GAP优化 return max_flow; d[u] = tp; if(u != s) u = pre[u]; } } return max_flow; } int main() { int F, D,n; scanf("%d %d %d", &n, &F, &D); /*1--->n n + 1--->2 * n是牛的编号 2 * n + 1 ---> 2 * n + F是食物编号 2 * n + F + 1 -----> 2 * n+ F + D是饮料标号 0是源点,2 * n+ F + D + 1是汇点, 共2 * n+ F + D + 2个点*/ N = 2 * n+ F + D + 2; s = 0; t = N - 1; int tn = n << 1; //for(int i = 0; i <= t; ++i) // cap[i][i] = 1;//所谓的点权 for(int i = 1; i <= F; ++i) cap[s][i + tn] = 1;//_add(s, i + tn, 1, 0);//食物和源点连线 for(int i = 1; i <= D; ++i) cap[tn + F + i][t] = 1;//_add(tn + F + i, t, 1, 0);//饮料和汇点连线 for(int i = 1; i <= n; ++i) cap[i][i + n] = 1;//_add(i, i + n, 1, 0);//拆点后的AB牛群之间两两连线 int f, d; for(int i = 1; i <= n ;++i) { int v; scanf("%d %d", &f, &d); while(f--) { scanf("%d", &v); cap[v + tn][i] = 1;//A牛群和食物连线:方向是食物到A牛群 } while(d--) { scanf("%d", &v); cap[i + n][v + tn + F] = 1;//饮料和B牛群连线:方向是B牛群到饮料 } } init(s, t); printf("%d/n", P_R(s, t)); return 0; }