POJ 3281 Dining (USACO 2007 Open Gold)
题目意思比较简单,就是说现在有 N 只奶牛,F 种食物和 D 种饮料,每只奶牛喜欢其中的一些食物和饮料。现在每种食物和饮料只能分给一只奶牛,每只奶牛也只能吃一种食物和一种饮料,问最多能使多少奶牛既吃到食物又喝到饮料。
这个题和二分图匹配有相似之处,但又不完全相同,我们可以沿着二分图匹配的建模方式继续思考。
由于有 N 只奶牛、F 种食物和 D 种饮料,因此我们可以将这些东西抽象成图中的点。为了方便,我们将食物放在左边,奶牛放在中间,饮料放在右边。沿用前面的建模方式,由于食物和饮料的使用限制,我们从源点向每种食物连一条边,从每种饮料向汇点连一条边,容量都为 1。而每只奶牛都有喜欢的食物和饮料,因此将每只奶牛喜欢的食物连到这只奶牛,从这只奶牛连到每种它喜欢的饮料。
但这样是否就对了呢?实际还是有问题的,因为经过每只奶牛的食物可能超过一种,这就意味着每只奶牛可能会吃超过一组的食物和饮料,而这在题目中是不允许的。
怎 么 解 决 这 个 问 题 呢 ? 我 们 又 回 到 了 流 的 基 本 性 质 : 容 量 限 制f(u,v)<=c(u,v) 。因此我们将每只奶牛拆成两个点,同一只奶牛的两个点之间连边,容量为 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));
// copy (cur, adj);//复制,当前弧优化
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++)
{
// cout<<"666"<<endl;
add(source,i,1);//原点到食物
}
for(int i=1;i<=drink;i++)//饮料到汇点
{
// cout<<"hahhaa"<<endl;
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;
} 类似的题:
You, a part-time dining service worker in your college’s dining hall, are now confused with a new problem: serve as many people as possible.
The issue comes up as people in your college are more and more difficult to serve with meal: They eat only some certain kinds of food and drink, and with requirement unsatisfied, go away directly.
You have prepared F (1 <= F <= 200) kinds of food and D (1 <= D <= 200) kinds of drink. Each kind of food or drink has certain amount, that is, how many people could this food or drink serve. Besides, You know there’re
N (1 <= N <= 200) people and you too can tell people’s personal preference for food and drink.
Back to your goal: to serve as many people as possible. So you must decide a plan where some people are served while requirements of the rest of them are unmet. You should notice that, when one’s requirement is unmet, he/she
would just go away, refusing any service.
Input
There are several test cases.
For each test case, the first line contains three numbers: N,F,D, denoting the number of people, food, and drink.
The second line contains F integers, the ith number of which denotes amount of representative food.
The third line contains D integers, the ith number of which denotes amount of representative drink.
Following is N line, each consisting of a string of length F. e jth character in the ith one of these lines denotes whether people i would accept food j. “Y” for yes and “N” for no.
Following is N line, each consisting of a string of length D. e jth character in the ith one of these lines denotes whether people i would accept drink j. “Y” for yes and “N” for no.
Please process until EOF (End Of File).
Output
For each test case, please print a single line with one integer, the maximum number of people to be satisfied.
Sample Input
4 3 3
1 1 1
1 1 1
YYN
NYY
YNY
YNY
YNY
YYN
YYN
NNY
Sample Output
3
代码:
注意是多组输入~
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxE = 200000;
const int maxN = 40005;
const int maxQ = 40000;
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));
// copy (cur, adj);//复制,当前弧优化
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 P,F,D;
while(~scanf("%d%d%d",&P,&F,&D))
{
init();
int c;
char x;
source=0;
sink=F+2*P+D+1;
nv=sink+1;
for(int i=1;i<=F;i++)
{
cin>>c;
add(source,i,c);
}
for(int i=1;i<=D;i++)
{
cin>>c;
add(F+2*P+i,sink,c);
}
for(int i=1;i<=P;i++)
{
add(F+i,F+P+i,1);
}
for(int i=1;i<=P;i++)
{
for(int j=1;j<=F;j++)
{
cin>>x;
if(x=='Y')
add(j,F+i,1);
}
}
for(int i=1;i<=P;i++)
{
for(int j=1;j<=D;j++)
{
cin>>x;
if(x=='Y')
{
add(F+P+i,F+2*P+j,1);
}
}
}
cout<<ISAP()<<endl;
}
}
1477



被折叠的 条评论
为什么被折叠?



