1.题目链接。题目大意:N头牛,F种食物和D种饮料,每头牛都有一些喜欢的饮料和食物,现在做一下分配,每头牛只能吃一种食物和喝一种饮料,没中过食物喝饮料也只能被一头牛拥有。问最多能够满足多少头牛的需求。
2.一个类似二分图的匹配问题,对于每种食物,把它和喜欢它的牛连边,每种饮料也是如此。这样直接跑最大流是不可行的,因为这样直接流可能会让一头牛得到两种食物,或者喝到两种饮料,其实这就涉及到了一个问题:限流。把每头牛拆点,中间连一条边,容量为1,这样拆点限流就让流入喝流出的流都是1,流过该边上的流量小于等于1.
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxE = 200000;
const int maxN = 5005;
const int maxQ = 10000;
const int oo = 0x3f3f3f3f;
struct Edge {
int v, c, n;
};
Edge edge[maxE];
int adj[maxN], cntE;
int Q[maxE], head, tail, inq[maxN];
int d[maxN], num[maxN], cur[maxN], pre[maxN];
int sink, source, nv;
void add(int u, int v, int c) {//添加边
//正向边
edge[cntE].v = v;
edge[cntE].c = c;//正向弧的容量为c
edge[cntE].n = adj[u];
adj[u] = cntE++;
//反向边
edge[cntE].v = u;
edge[cntE].c = 0;//反向弧的容量为0
edge[cntE].n = adj[v];
adj[v] = cntE++;
}
void rev_bfs() {//反向BFS标号
memset(num, 0, sizeof(num));
memset(d, -1, sizeof(d));
//clear (d, -1);//没标过号则为-1
d[sink] = 0;//汇点默认为标过号
num[0] = 1;
head = tail = 0;
Q[tail++] = sink;
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()
{
memcpy(cur, adj, sizeof(cur));
rev_bfs();//只用标号一次就够了,重标号在ISAP主函数中进行就行了
int flow = 0, u = pre[source] = source, i;
while (d[sink] < nv)
{
//最长也就是一条链,其中最大的标号只会是nv - 1,如果大于等于nv了说明中间已经断层了。
if (u == sink) {//如果已经找到了一条增广路,则沿着增广路修改流量
int f = oo, neck;
for (i = source; i != sink; i = edge[cur[i]].v) {
if (f > edge[cur[i]].c) {
f = edge[cur[i]].c;//不断更新需要减少的流量
neck = i;//记录回退点,目的是为了不用再回到起点重新找
}
}
for (i = source; i != sink; 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;//GAP间隙优化,如果出现断层,可以知道一定不会再有增广路了
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()
{
memset(adj, -1, sizeof(adj));
cntE = 0;
}
int main()
{
int cow, food, drink;
int a, b;
int fff, ddd;
scanf("%d%d%d", &cow, &food, &drink);
init();
sink = food + 2 * cow + drink + 1;
source = 0;
nv = sink + 1;
for (int i = 1; i <= food; i++)
{
add(source, i, 1);//原点到食物
}
for (int i = 1; i <= drink; i++)//饮料到汇点
{
add(i + food + 2 * cow, sink, 1);
}
for (int i = 1; i <= cow; i++)
{
add(i + food, i + food + cow, 1);//牛与牛之间建边
cin >> a >> b;
while (a--)
{
cin >> fff;
add(fff, i + food, 1);
}
while (b--)
{
cin >> ddd;
add(food + cow + i, food + cow * 2 + ddd, 1);
}
}
cout << ISAP() << endl;
}