【题目大意】
有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()); } }