【题目大意】
有M个猪圈,每个猪圈里初始时有若干头猪。一开始所有猪圈都是关闭的。依次来了N个顾客,每个顾客分别会打开指定的几个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。问总共最多能卖出多少头猪。(1 <= N <= 100, 1 <= M <= 1000)
【建模方法】
不难想象,这个问题的网络模型可以很直观地构造出来。就拿上面的例子来说,可以构造出图1所示的模型(图中凡是没有标数字的边,容量都是∞):
• 三个顾客,就有三轮交易,每一轮分别都有3个猪圈和1个顾客的结点。
• 从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。
• 从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。
• 在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是∞。
• 最后一轮除外,从每一轮的i号猪圈都有一条边连向下一轮的i号猪圈,容量都是∞,表示这一轮剩下的猪可以留到下一轮。
• 最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,表示它们之间可以任意流通。
节点合并
规律1. 如果几个结点的流量的来源完全相同,则可以把它们合并成一个。
规律2. 如果几个结点的流量的去向完全相同,则可以把它们合并成一个。
规律3. 如果从点u到点v有一条容量为∞的边,并且点v除了点u以外没有别的流量来源,则可以把这两个结点合并成一个。
让我们从图4中重新总结一下构造这个网络模型的规则:
• 每个顾客分别用一个结点来表示。
• 对于每个猪圈的第一个顾客,从源点向他连一条边,容量就是该猪圈里的猪的初始数量。如果从源点到一名顾客有多条边,则可以把它们合并成一条,容量相加。
• 对于每个猪圈,假设有n个顾客打开过它,则对所有整数i∈[1, n),从该猪圈的第i个顾客向第i + 1个顾客连一条边,容量为∞。
• 从各个顾客到汇点各有一条边,容量是各个顾客能买的数量上限。
<以上来自 [ 网络流建模汇总 ] [ Edelweiss ] Orz >
相当于首顾客是中转站, 是他可开猪圈中猪数之和.
之后的顾客从首顾客中取出(若涉及首顾客所代表的猪圈)
若有新圏被打开, 则该顾客将成为新圏的中转站,
每个顾客向汇点连一条边, 边权为可买数, 表示此顾客作为中转站可以经手的猪数很多, 但带回家的数目是有限的.
前向星
[Edmonds-Karp]
//156K 0MS
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
const int MAXM = 1005;
struct pool
{
int v,pre,w;
}p[MAXN*MAXN*2];
int num,head[MAXN],pig[MAXM],flow[MAXN],pre[MAXN],M,N,S,T,opened[MAXM];
bool vis[MAXN];
queue<int> q;
void clear()
{
num = 1;
memset(head,0,sizeof(head));
memset(opened,false,sizeof(opened));
}
void add(int u,int v,int w)
{
p[++num].v = v, p[num].w = w, p[num].pre = head[u], head[u] = num;
}
int bfs()
{
while(!q.empty()) q.pop();
memset(flow,0,sizeof(flow));
memset(vis,false,sizeof(vis));
vis[S] = true;
flow[S] = INF;
q.push(S);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int tmp=head[u],k;k=p[tmp].v,tmp;tmp=p[tmp].pre)
{
if(!vis[k]&&p[tmp].w>0)///选择可以流的管道进行更新
{
vis[k] = true;
q.push(k);
pre[k] = u;
flow[k] = min(p[tmp].w,flow[u]);
}
}
}
return flow[T];
}
int Edmonds_Karp()
{
int u,f,MaxFlow = 0;
while((f=bfs()))
{
for(u=T;u>S;u=pre[u])
{
for(int tmp=head[pre[u]],k;k=p[tmp].v,tmp;tmp=p[tmp].pre)
{
if(k!=u) continue;
p[tmp].w -= f;
p[tmp^1].w += f;
break;
}
}
MaxFlow += f;
}
return MaxFlow;
}
int main()
{
while(scanf("%d %d",&M,&N)==2)
{
S = 0;
T = N+1;
clear();
for(int i=1;i<=M;i++)
scanf("%d",pig+i);
for(int i=1,a,u;i<=N;i++)
{
scanf("%d",&a);
int MaxWeigh = 0;
while(a--)
{
scanf("%d",&u);
if(!opened[u])
{
MaxWeigh += pig[u];
opened[u] = i;
}
else
{
add(opened[u],i,INF);
add(i,opened[u],0);
}
}
if(MaxWeigh)
add(S,i,MaxWeigh), add(i,S,0);
scanf("%d",&a);
add(i,T,a);
add(T,i,0);
}
printf("%d\n",Edmonds_Karp());
}
}
[Dinic]
//140K 16MS
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int num, M, N, source, sink;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
const int MAXM = 1005;
int pig[MAXM],opened[MAXM];
/* The arc of the flow network.*/
struct Pool
{
int next, t, c;
} edge[MAXM];
/* The point of the flow network.*/
struct Point
{
int son, cur, pre, lim, d;
} a[MAXN];
int que[MAXN], fi, la;
bool vis[MAXN];
/* Build the hierarchical graph for the algorithm*/
bool build()
{
memset(vis, 0, sizeof (vis));
que[fi=la=0]=sink;//reverse
a[sink].d=0, a[sink].cur=a[sink].son, vis[sink]=true;
while (fi<=la)
{
int v=que[fi++];
for (int now=a[v].son, u; u=edge[now].t, now; now=edge[now].next)
if (edge[now^1].c&&!vis[u])//BFS来分层,这里和EK相同
{//倒着BFS的话,当然引用的还是对侧边,即正向边
a[u].d=a[v].d+1;//越向前标号渐大
a[u].cur=a[u].son;//cur指向头
vis[u]=true;//已遍历
que[++la]=u;//入队
}
if (vis[source])return true;//层次图向前已经扩展到源点
}
return false;
}
/*Use the Dinic algorithm to calculate the max flow.*/
int MaxFlow()
{
int u, v, now, ret=0;
while (build())
{
a[u=source].lim=INF;
while (true)
{
for (now=a[u].cur; v=edge[now].t, now; now=edge[now].next)//cur优化
if (edge[now].c&&a[u].d==a[v].d+1)break;//找到了一个子节点属于层次图
if (now)
{
a[u].cur=edge[now].next;//下一次从这一条边的下一条边开始dfs
a[v].pre=now;//指向v的边的指针
a[v].lim=min(a[u].lim, edge[now].c);///更新到此处为止流的上限
if ((u=v)==sink)//如果已经找到了一条增广路(走到了尽头)
///注意这个地方借判断语句, 将u下移, 便于判断为否的时候回到上面进入下一层!
{//进行增广
do
{
edge[a[u].pre].c-=a[sink].lim;
edge[a[u].pre^1].c+=a[sink].lim;//这两句和Edmonds-Karp是一样的,增广
u=edge[a[u].pre^1].t;//找前驱~!
} while (u!=source);
ret+=a[sink].lim;//增广完毕之后累加新找到的流
}//否则(没走到尽头)继续向下DFS
}
else//没有子节点属于层次图
{
if (u==source)break;//已经退到了源,则已找到最大流,算法结束
a[u].cur=now;//=0,此节点被废弃,子代亦然
u=edge[a[u].pre^1].t;//根据反向边找到前驱~!
}
}
}
return ret;
}
void clear()
{
num = 1;
memset(a, 0, sizeof (a));
memset(opened,false,sizeof(opened));
}
/* Add an arc to the flow network.*/
void add(int x, int y, int z)
{
edge[++num].t=y;
edge[num].c=z;
edge[num].next=a[x].son;//相当于pool的head数组
a[x].son=num;
}
int main()
{
while(scanf("%d %d",&M,&N)==2)
{
source = 0;
sink = N+1;
clear();
for(int i=1;i<=M;i++)
scanf("%d",pig+i);
for(int i=1,a,u;i<=N;i++)
{
scanf("%d",&a);
int MaxWeigh = 0;
while(a--)
{
scanf("%d",&u);
if(!opened[u])
{
MaxWeigh += pig[u];
opened[u] = i;
}
else
{
add(opened[u],i,INF);
add(i,opened[u],0);
}
}
if(MaxWeigh)
add(source,i,MaxWeigh), add(i,source,0);
scanf("%d",&a);
add(i,sink,a);
add(sink,i,0);
}
printf("%d\n",MaxFlow());
}
}