题目来源:http://poj.org/problem?id=1149
题目大意:
Mirko在猪场工作,有N个猪圈,每个猪圈有pig[i]个猪,但是他不能打开任何猪圈的门,有M个人按顺序来买猪,每个人有K个猪圈的钥匙,有一个最大的可能要买的数量buy[i],但是Mirko 可以卖给他任意数量的猪(当然不能超过MAX),这个人走后Mikro可以重新分配这K个猪圈里的猪,然后猪圈又会把门锁起来,问你最终,Mikro可以买最多的猪的数量。
题目分析:
这道题是一道经典的构图题,题的难点在于怎么构图,然后就是简单的最大流的问题了。
构图原则及分析:
1.首先源点设为0,汇点设为M+1,M个人设为网络流中其他的点,N个猪圈什么都不算,只用在构图上!
2.假设第i个猪圈有cnt[i]个人有钥匙,并且这cnt[i]个人的编号分别是key[1]<key[2]<.....<key[cnt[i]],如果当前源点和key1之间没有边,那么连一条源点到key1的边,容量为pig[key1],如果当前源点和key1之间有边,则在这条边上容量加上pig[i];然后在key[i]和key[i+1](1<=i<cnt[i])之间连一条容量为INF的边。
3.每个卖家i(1<=i<=M)到汇点之间连一条容量为buy[i]的边.。
构图完毕!
可是为什么这样构图呢?可以简单的分析一下:
首先对于某一个猪圈i,如果第j个人是第一个有这个猪圈的钥匙的卖家,那么对于总的交易量来说可能增加的最大数目为pig[i],因为之前所有的人都没有这个猪圈的钥匙,所以这个猪圈的猪从没有被卖过或者调换过。而且这个增量是由第j个人实现的,所以效果就是源点和他的边的容量加上pig[i]。
但是这并没有包含Mikro可以“使坏”的操作,即猪圈之间的调换,假设第i个人有p,q猪圈的钥匙,第i+1个人有p,r的钥匙,那么是不是意味着第i+1个人可以买p,q,r三个猪圈的所有的猪,也就是说第i个人能买的第i+1个都能买!所以i和i+1这条边的容量应该是无限大的!
然后就是实现的问题了,我用的dinic
代码实现:
#define MAXPIG 1010
#include <cstdio>
#include <cstring>
#include <climits>
#define INF 99999999
using namespace std;
int N,M;
const int MAXN=110,MAXM=440000;
int pig[MAXPIG];
int key[MAXPIG][MAXN];
int cnt[MAXPIG];
int buy[MAXN];
int Sbuyer[MAXN];
struct Dinic
{
struct edge
{
int x,y;//两个顶点
int c;//容量
int f;//当前流量
edge *next,*back;//下一条边,反向边
edge(int x,int y,int c,edge* next):x(x),y(y),c(c),f(0),next(next),back(0) {}
void* operator new(size_t, void *p)
{
return p;
}
}*E[MAXN],*data;//E[i]保存顶点i的边表
char storage[2*MAXM*sizeof(edge)];
int S,T;//源、汇
int Q[MAXN];//DFS用到的queue
int D[MAXN];//距离标号,-1表示不可达
void DFS()
{
memset(D,-1,sizeof(D));
int head=0,tail=0;
Q[tail++]=S;
D[S]=0;
for(;;)
{
int i=Q[head++];
for(edge* e=E[i]; e; e=e->next)
{
if(e->c==0)continue;
int j=e->y;
if(D[j]==-1)
{
D[j]=D[i]+1;
Q[tail++]=j;
if(j==T)return;
}
}
if(head==tail)break;
}
}
edge* cur[MAXN];//当前弧
edge* path[MAXN];//当前找到的增广路
int flow()
{
int res=0;//结果,即总流量
int path_n;//path的大小
for(;;)
{
DFS();
if(D[T]==-1)break;
memcpy(cur,E,sizeof(E));
path_n=0;
int i=S;
for(;;)
{
if(i==T) //已找到一条增广路,增广之
{
int mink=0;
int delta=INT_MAX;
for(int k=0; k<path_n; ++k)
{
if(path[k]->c < delta)
{
delta = path[k]->c;
mink=k;
}
}
for(int k=0; k<path_n; ++k)
{
path[k]->c -= delta;
path[k]->back->c += delta;
}
path_n=mink;//回退
i=path[path_n]->x;
res+=delta;
}
edge* e;
for(e=cur[i]; e; e=e->next)
{
if(e->c==0)continue;
int j=e->y;
if(D[i]+1==D[j])break;//找到一条弧,加到路径里
}
cur[i]=e;//当前弧结构,访问过的不能增广的弧不会再访问
if(e)
{
path[path_n++]=e;
i=e->y;
}
else //该节点已没有任何可增广的弧,从图中删去,回退一步
{
D[i]=-1;
if(path_n==0)break;
path_n--;
i=path[path_n]->x;
}
}
}
return res;
}
int cut(int* s)
{
int rst=0;
for(int i=0; i<MAXN; ++i)
if(D[i]==-1&&E[i])
s[rst++]=i;
return rst;
}
void init(int _S,int _T)
{
S=_S,T=_T;
data=(edge*)storage;
memset(E,0,sizeof(E));
}
void add_edge(int x,int y,int w) //加进一条x至y容量为w的边,需要保证0<=x,y<MAXN,0<w<=INT_MAX
{
E[x]=new((void*)data++) edge(x,y,w,E[x]);
E[y]=new((void*)data++) edge(y,x,0,E[y]);
E[x]->back = E[y];
E[y]->back = E[x];
}
};
Dinic dinic;
int main()
{
scanf("%d%d",&N,&M);
int rst=0;
int S=0,T=M+1;
char c;
dinic.init(S,T);
memset(cnt,0,sizeof(cnt));
memset(Sbuyer,0,sizeof(Sbuyer));
for(int i=1; i<=N; ++i)
{
scanf("%d",&pig[i]);
}
int K;
for(int i=1;i<=M;i++)
{
scanf("%d",&K);
int x;
for(int j=1;j<=K;j++)
{
scanf("%d",&x);
key[x][cnt[x]++] = i;
}
scanf("%d",&buy[i]);
}
for(int i=1;i<=N;i++)
{
if(cnt[i] > 0)
Sbuyer[key[i][0]] += pig[i];
//printf("%d ",Sbuyer[key[i][0]]);
}
for(int i=1;i<=M;i++)
{
dinic.add_edge(i,T,buy[i]);
//printf("%d ",Sbuyer[i]);
if(Sbuyer[i]>0)
dinic.add_edge(S,i,Sbuyer[i]);
}
for(int i=1;i<=N;i++)
{
for(int j=0;j<cnt[i]-1;j++)
{
//printf("%d %d\n",key[i][j],key[i][j+1]);
dinic.add_edge(key[i][j],key[i][j+1],INF);
}
}
rst=dinic.flow();
printf("%d\n",rst);
return 0;
}