链接:http://poj.org/problem?id=1149
题意:迈克有一个养猪场,有m个猪圈,每个猪圈都上了锁,但是迈克没有钥匙他不能打开猪圈,要买猪的顾客一个接一个来养猪场,每个人有一些猪圈的钥匙,他们要买一定数目的猪,如果顾客要来买猪,他们会提前告诉迈克:他们所拥有的钥匙数量及对应哪些猪圈、要购买的数量,这样迈克就能安排销售计划使卖出的猪最多。
当每个顾客来的时候,将那些他拥有钥匙的猪圈门全部打开,迈克从猪圈中卖出一些猪给他们,如果迈克愿意,他可以重新分配这些被打开的猪圈中的猪,当顾客离开时猪圈再次被锁上。猪圈可容纳的猪无上限。求迈克这一天最多能卖多少猪。
昨天下午开始看网络流,看了很久,感觉理解了增广路的方法和预流推进的方法,不过一下接触的概念太多,有点迷糊。。。今天终于能做题了,看到这道题,毫无想法。。。终于知道了为什么大家都说网络流建图难,剩下的套模板就行了。我现在连模板都不会敲,更别说建图了。参考图论书上的解法和代码,一点一点理解了这道题,但是思路是书上的,代码是照着书上敲的。。
分析:
本题的关键在于如何构造一个容量网络。在本题中,容量网络的构造方法如下:
1) 将顾客看作除源点和汇点以外的结点,另设2个结点:源点和汇点;
2) 源点和每个猪圈的第一个顾客连边,边的权是开始时猪圈中猪的数目;
3) 若源点和某个结点之间有重边,则将权合并(因此源点流出的流量就是所有的猪圈能提供的猪的数目);
4) 顾客j紧跟在顾客i之后打开某个猪圈,则边<i, j>的权是+∞;这是因为,如果顾客j紧跟在顾客i 之后打开某个猪圈,那么迈克就有可能根据顾客j 的需求将其他猪圈中的猪调整到该猪圈,这样顾客j 就能买到尽可能多的猪;
5) 每个顾客和汇点之间连边,边的权是顾客所希望购买的猪的数目(因此汇点的流入量就是每个顾客所购买的猪的数目)。
这是建图的思路,是图论书上的,对熟练掌握网络最大流的人来说,剩下的就比较简单了,不过对我来说,代码还得照着书一点点敲。
算法用的是增广路的算法,没有使用DFS,我把书中自己写的队列改成了stl的队列,剩下基本都和书里一样
作为我第一道照着书敲下来的网络流题目,我把注释都写的很详细
#include<cstring>
#include<string>
#include<fstream>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<stack>
#include<ctime>
#include<cstdlib>
#include<functional>
#include<cmath>
using namespace std;
#define PI acos(-1.0)
#define MAXN 100100
#define eps 1e-7
#define INF 0x7FFFFFFF
#define seed 131
#define ll long long
#define ull unsigned ll
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
int s,t; //s源点,t汇点
int customer[110][110]; //节点间容量
int flow[110][110]; //节点间流量
int pigs[1010]; //每个猪圈中猪的数量
int last[1010]; //每个猪圈前一个顾客的序号
int n,m; //n顾客数目,m猪圈数目
void ford(){
int prev[110]; //增广路上该顶点前一个顶点的序号,初始为-2未标号,源点标号-1
int minflow[110]; //每个顶点的可改进量α
minflow[0] = INF;
int p;
queue<int>q;
while(1){ //标号法
for(int i=0;i<110;i++) prev[i] = -2;
prev[0] = -1;
while(!q.empty()) q.pop();
q.push(0);
while(!q.empty()&&prev[t]==-2){
int v = q.front();
q.pop();
for(int i=0;i<t+1;i++){
//如果顶点i是顶点v的邻接顶点,则考虑是否对顶点i进行标号
//customer[v][i]-flow[v][i]!=0能保证顶点i是顶点v的邻接顶点且能标号
if(prev[i]==-2&&(p=customer[v][i]-flow[v][i])){ //顶点i未标号,且Cij-Fij>0
prev[i] = v;
q.push(i);
minflow[i] = min(minflow[v],p);
}
}
}
if(prev[t]==-2) break; //汇点t没有标号,标号法结束
int j;
for(int i=prev[t],j=t;i!=-1;j=i,i=prev[i]){ //调整增广路流量
flow[i][j] = flow[i][j] + minflow[t];
flow[j][i] = -flow[i][j];
}
}
p = 0;
for(int i=0;i<t;i++) //统计进入汇点的流量,即为最大流的流量
p = p + flow[i][t];
printf("%d\n",p);
}
int main(){
int k,num; //k第k个猪圈的钥匙,num每个顾客拥有钥匙的数目
int i,j;
memset(last,0,sizeof(last));
memset(customer,0,sizeof(customer));
memset(flow,0,sizeof(flow));
scanf("%d%d",&m,&n);
s = 0;
t = n + 1;
for(i=1;i<=m;i++) scanf("%d",&pigs[i]);
for(i=1;i<=n;i++){ //构造网络流
scanf("%d",&num);
for(j=0;j<num;j++){
scanf("%d",&k);
if(last[k]==0) //第i个顾客是第k个猪圈的第一个顾客
customer[s][i] = customer[s][i] + pigs[k];
else //顾客i紧跟在顾客last[k]后面打开第k个猪圈
customer[last[k]][i] = INF;
last[k] = i;
}
scanf("%d",&customer[i][t]); //每个顾客到汇点的边,权值为顾客购买猪的数量
}
ford();
return 0;
}