有M个猪圈,每个猪圈里初始时有若干头猪。一开始所有猪圈都是关闭的。依次来了N个顾客,每个顾客分别会打开指定的几个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。问总共最多能卖出多少头猪。
思路:网络流建模的经典题目。这个模型不是一蹴而就的,我们可以从最原始的模型一步一步简化得到。
最直观的,就是建N层网络,每层有M个点,表示M个猪圈。每层表示一个顾客。源点向初始的每个猪圈连容量为其猪的个数的边。每个顾客向汇点连容量为其购买量的边。
对于每个顾客,他打开的猪圈向下一层连容量为无穷大的边。
但是这样,我们计算一下点的个数: N * M <= 10 ^ 5,点的个数非常多,可能会超时。
现在我们要对这个网络进行优化。
对于网络,我们有以下性质:
性质1:如果一些点的流量的来源完全相同,那他们就能合并成一个节点。
性质2:如果一些点的流量的去向完全相同,那他们就能合并成一个节点。
性质3:如果u到v有一条容量为无穷大的边,且v点没有其他的流量来源,那么v点就可以合并到u点。(一个点可以拆成两个流量为无穷大的点。)
这样,我们就能对这个网络中的点进行合并。
合并后,我们发现,模型是这样的:
1.源点向每个第一个打开某个猪圈的顾客连容量为猪圈的大小的边。
2.对于每个猪圈,前一名顾客向后一名顾客连容量为无穷大的边。
3.对于每个顾客,向汇点连容量为购买量的边。
对于这个模型,我们可以这样解释:对于每个猪圈的第一名顾客,他能买的最大的量就是猪圈的容量。而,对于后面的顾客,他能买的最大的量是由上一个顾客留下来的。
代码如下:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
struct edge{
int from,to;
int cap,flow;
edge(int u =0,int v =0,int c=0,int f=0):from(u),to(v),cap(c),flow(f){}
};
struct ISAP{
static const int INF = 0x3f3f3f3f;
static const int MAX = 5000 * 2;//2倍的边的大小
int head[MAX];//每个节点对应链表的开始位置
int next[MAX];//链表的下一个节点在edges数组的位置
int tot;//edges数组的大小
edge edges[MAX];//储存边的数组
int que[MAX],front,tail;//队列,保存节点
int d[MAX];//距离标号
bool vis[MAX];//访问标记
int num[MAX];//gap优化
int pre[MAX];//增广路中,节点X的前面一个弧的标号
int cur[MAX];//对于每个节点的,处理的当前弧。
int s,t,n;//s源点标号,t汇点标号,n节点总数
void init(int n){
this->n = n;//注意此处的下标问题
memset(head,-1,sizeof(int)*(n+1));
tot = 0;//
}
void addedge(int from,int to, int cap){
edges[tot] = edge(from,to,cap,0);
next[tot] = head[from], head[from] = tot++;
edges[tot] = edge(to,from,0,0);
next[tot] = head[to],head[to] = tot++;
}
void bfs(){
memset(vis,0,sizeof(vis));
front = tail = 0;
d[t] = 0;
vis[t] = true;
que[tail++] = t;
while(front < tail){
int u = que[front++];
for(int v = head[u]; v != -1; v = next[v]){
edge & e = edges[v^1];
if(e.cap > e.flow && !vis[e.from]){//对处于残余网络中的弧且没访问过的节点处理
d[e.from] = d[u] + 1;
vis[e.from] = true;
que[tail++] = e.from;
}
}
}
}
int augment(){
int x = t,a = INF;
while(x != s){
edge& e = edges[pre[x]];
a = min(a,e.cap - e.flow);
x = e.from;
}
x = t;
while(x != s){
edges[pre[x]].flow += a;
edges[pre[x]^1].flow -= a;
x = edges[pre[x]].from;
}
return a;
}
int maxflow(int s, int t){
this->s = s, this->t = t;
memset(num,0,sizeof(num));
int flow = 0;
bfs();
for(int i = 0; i <= n; ++i){//注意此处的下标问题
num[d[i]]++;
cur[i] = head[i];
}
int x = s;
while(d[s] < n){
if(x == t){
flow += augment();
x = s;
}
bool ok = false;
for(int &v = cur[x]; v != -1; v = next[v]){
edge& e = edges[v];
if(e.cap > e.flow && d[x] == d[e.to] + 1){
ok = true;
pre[x = e.to] = v;
break;
}
}
if(!ok){
int m = n - 1;
for(int v = head[x]; v != -1; v = next[v]){
edge & e = edges[v];
if(e.cap > e.flow) m = min(m,d[e.to]);
}
if(--num[d[x]] == 0) break;
num[d[x]=m+1]++;
cur[x] = head[x];
if(x != s) x = edges[pre[x]].from;
}
}
return flow;
}
} solver;
const int MAX = 1010;
const int INF = 0x3f3f3f3f;
int N,M,A,K;
int cow[MAX];
int buy[MAX];
vector<int> G[MAX];
int main(void)
{
//freopen("input.txt","r",stdin);
scanf("%d %d",&M,&N);
for(int i = 1; i <= M; ++i)
G[i].clear();
for(int i = 1; i <= M; ++i)
scanf("%d", &cow[i]);
for(int i = 1; i <= N; ++i){
scanf("%d",&A);
for(int j = 1; j <= A; ++j){
scanf("%d", &K);
G[K].push_back(i);
}
scanf("%d",&buy[i]);
}
solver.init(N + 1);
int s = 0, t = N + 1;
for(int i = 1; i <= M; ++i){
if(G[i].size() > 0)
solver.addedge(s,G[i][0],cow[i]);
for(int j = 1, sz = G[i].size(); j < sz; ++j)
solver.addedge(G[i][j-1],G[i][j],INF);
}
for(int i = 1; i <= N; ++i)
solver.addedge(i,t,buy[i]);
printf("%d\n",solver.maxflow(s,t));
return 0;
}