POJ 3281 Dining ( 最大流 && 建图 )
题意 : 有 N 头牛,John 可以制作 F 种食物和 D 种饮料, 然后接下来有 N 行,每行代表一头牛的喜好==>开头两个数 Fi 和 Di 表示这头牛喜欢 Fi 种食物, Di 种饮料,接下来 Fi 个数表示喜欢的食物编号,Di 个数表示喜欢的饮料的编号,现在 John 要使用最优决策制作出 F 种食物和 D 种饮料,问怎么喂才能使尽可能多的牛喂饱 ( 喂饱 = 一份食物一份饮料,且一头牛最多消耗一份食物和一份饮料 ),最后输出最多喂饱的牛数。
分析 : 能将限制关系转化为图再用最大流求解真是太强了
做法是先抽象出一个源点和一个汇点
再抽象出 F 个点代表 F 种食物,后将源点和这 F 个点连上容量为 1 的边
接下来需要根据牛和其喜欢的食物进行关系连边,但是这里注意一个问题,每头牛只能选一种食物or饮料
这个限制即如果将牛抽象为点的话,那么次点只能经过一次
此限制解决方案便是拆点,用 2*N 个点代表 N 头牛,令每头牛拆出来的两个点为 (牛1) 、(牛2),只要 (牛1) 和 (牛2) 连上容量为 1 的边即可
接下来根据牛和其喜欢食物这一信息将各种食物所代表的点和拆点后的 (牛1) 连上容量为 1 的边
再再抽象出 D 个点代表 D 种饮料,然后根据牛及其喜欢的饮料这一信息将 (牛2) 和 D 种饮料对应连上容量为 1 的边
最后将所有的 D 种饮料所代表的点和汇点连上容量为 1 的边
最后就变成了 : 源点 => 食物 => 牛1 => 牛2 => 饮料 => 汇点
根据这副图去跑出来的最大流就是答案
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<stdio.h> #include<string.h> #include<vector> #include<algorithm> #include<queue> using namespace std; const int maxn = 500 + 10; const int INF = 100000000; int Food, Drink, Cows; int F[maxn][maxn], D[maxn][maxn], idx1[maxn], idx2[maxn]; struct Edge { int from,to,cap,flow; Edge(){} Edge(int from,int to,int cap,int flow):from(from),to(to),cap(cap),flow(flow){} }; struct Dinic { int n,m,s,t; //结点数,边数(包括反向弧),源点与汇点编号 vector<Edge> edges; //边表 edges[e]和edges[e^1]互为反向弧 vector<int> G[maxn]; //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号 bool vis[maxn]; //BFS使用,标记一个节点是否被遍历过 int d[maxn]; //d[i]表从起点s到i点的距离(层次) int cur[maxn]; //cur[i]表当前正访问i节点的第cur[i]条弧 void init(int n,int s,int t) { this->n=n,this->s=s,this->t=t; for(int i=0;i<=n;i++) G[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap) { edges.push_back( Edge(from,to,cap,0) ); edges.push_back( Edge(to,from,0,0) ); m = edges.size(); G[from].push_back(m-2); G[to].push_back(m-1); } bool BFS() { memset(vis,0,sizeof(vis)); queue<int> Q;//用来保存节点编号的 Q.push(s); d[s]=0; vis[s]=true; while(!Q.empty()) { int x=Q.front(); Q.pop(); for(int i=0; i<G[x].size(); i++) { Edge& e=edges[G[x][i]]; if(!vis[e.to] && e.cap>e.flow) { vis[e.to]=true; d[e.to] = d[x]+1; Q.push(e.to); } } } return vis[t]; } //a表示从s到x目前为止所有弧的最小残量 //flow表示从x到t的最小残量 int DFS(int x,int a) { //printf("%d %d\n", x, a); if(x==t || a==0)return a; int flow=0,f;//flow用来记录从x到t的最小残量 for(int& i=cur[x]; i<G[x].size(); i++) { Edge& e=edges[G[x][i]]; if(d[x]+1==d[e.to] && (f=DFS( e.to,min(a,e.cap-e.flow) ) )>0 ) { e.flow +=f; edges[G[x][i]^1].flow -=f; flow += f; a -= f; if(a==0) break; } } return flow; } int Maxflow() { int flow=0; while(BFS()) { memset(cur,0,sizeof(cur)); flow += DFS(s,INF); } return flow; } }DC; int main(void) { while(~scanf("%d %d %d", &Cows, &Food, &Drink)){ memset(idx1, 0, sizeof(idx1)); memset(idx2, 0, sizeof(idx2)); memset(F, 0, sizeof(F)); memset(D, 0, sizeof(D)); int N = Food + Drink + Cows*2 + 1; for(int i=1; i<=Cows; i++){ scanf("%d %d", &idx1[i], &idx2[i]); for(int j=1; j<=idx1[i]; j++) scanf("%d", &F[i][j]); for(int j=1; j<=idx2[i]; j++) scanf("%d", &D[i][j]); } DC.init(N+1, 0, N); for(int i=1; i<=Food; i++) DC.AddEdge(0, i, 1);///源点连食物 for(int i=1; i<=Drink; i++) DC.AddEdge(Food+Cows*2+i, N, 1);///(牛2)连饮料 for(int i=1; i<=Cows; i++) DC.AddEdge(Food+i, Food+Cows+i, 1);///(牛1)连(牛2) for(int i=1; i<=Cows; i++){ for(int j=1; j<=idx1[i]; j++) DC.AddEdge(F[i][j], Food+i, 1);///食物连(牛1) for(int j=1; j<=idx2[i]; j++) DC.AddEdge(Food+Cows+i ,Food+Cows*2+D[i][j], 1);///(牛2)连饮料 } printf("%d\n", DC.Maxflow()); } return 0; }
在 POJ 的 Discuss 上看到了一些其他的观点,放在这里说一下
① 能否变成匹配问题求最大匹配解决此题?
不能 如
1 2 2
2 2 1 2 1 2
做匹配为二,但实际只有一头牛,所以只需一份套餐
Posted by:20053565 at 2007-07-21 12:41:40
② 为什么不能拆食物,变成:源点 => 牛 => 食物1 => 食物2 => 饮料 => 汇点
拆食物错的原因在于:
如果拆食物,按照牛->食物-in->食物-out->饮料 这样建图的话,虽然保证了流量的限制,每头牛也只能选一种食物和饮料。
但是食物-out->饮料之间的边就失去的牛的编号性,就成了没有针对哪头牛的组合,不知道某些边是哪些牛的喜好。
这样就会导致某头牛明明不喜欢某种搭配,但是因为有其他牛喜欢这种搭配,使得这头牛“强行”被选择了这种搭配。自然就WA了。
Posted by:longmenwaideyu at 2011-06-02 14:12:02