【题意】:有 M 个猪圈(M ≤ 1000),每个猪圈里初始时有若干头猪。开始所有猪圈都是关闭的。依次来了 N 个顾客(N ≤ 100),每个顾客分别会打开指定的几个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。 问总共最多能卖出多少头猪。
【题解】:举个例子来说。有 3 个猪圈,初始时分别有 3、 1 和 10 头猪。依次来了 3 个顾客,第一个打开 1 号 和 2 号猪圈,最多买 2 头;第二个打开 1 号 和 3 号猪圈,最多买 3 头;第三个打开 2 号猪圈,最多买 6 头。那么,最好的可能性之一就是第一个顾客从 1 号圈买 2 头,然后把 1 号圈剩下的 1 头放到 2 号圈;第二个顾客从 3 号圈买 3 头;第三个顾客从 2 号圈买 2 头。总共卖出 2 + 3 + 2 = 7 头。□
不难想像,这个问题的网络模型可以很直观地构造出来。就拿上面的例子来说,可以构造出图1所示的模型(图中凡是没有标数字的边,容量都是 +∞):
- 三个顾客,就有三轮交易,每一轮分别都有 3 个猪圈和 1 个顾客的节点。
- 从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。
- 从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。
- 在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是 +∞。
- 最后一轮除外,从每一轮的 i 号猪圈都有一条边连向下一轮的 i 号猪圈,容量都是 +∞,表示这一轮剩下的猪可以留到下一轮。
- 最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,表示它们之间可以任意流通。
图 1
不难想像,这个网络模型的最大流量就是最多能卖出的数量。图中最多有 2 + N + M × N ≈ 100,000 个节点。□
这个模型虽然很直观,但是节点数太多了,计算速度肯定会很慢。其实不用再想别的算法,就让我们继续上面的例子,用合并的方法来简化这个网络模型。
首先,最后一轮中没有打开的猪圈就可以从图中删掉了,也就是中图2
红色的部分,显然它们对整个网络的流量没有任何影响。
图 2
接着,看
图 2 中
蓝色的部分。根据我总结出的以下几个规律,可以把这 4 个点合并成一个:
规律 1. 如果几个节点的流量的
来源完全相同,则可以把它们合并成一个。
规律 2. 如果几个节点的流量的
去向完全相同,则可以把它们合并成一个。
规律 3. 如果从点 u 到点 v 有一条容量为 +∞ 的边,并且 u 是 v 的
唯一流量
来源,或者 v 是 u 的
唯一流量
去向,则可以把 u 和 v 合并成一个节点。
根据规律1,可以把
蓝色部分右边的 1、 2 号节点合并成一个;根据规律2,可以把
蓝色部分左边的 1、 2 号节点合并成一个;最后,根据规律3,可以把
蓝色部分的左边和右边(已经分别合并成了一个节点)合并成一个节点。于是,图2被简化成了图3的样子。也就是说,最后一轮除外,每一轮被打开的猪圈和下一轮的同样这些猪圈都可以被合并成一个点。
图 3
接着,根据,图3中的蓝色节点、2 号猪圈和 1 号顾客这三点可以合并成一个;图3中的两个 3 号猪圈和 2 号顾客也可以合并成一个点。当然,如果两点之间有多条同向的边,则这些边可以合并成一条,容量相加,这个道理很简单,就不用我多说了。最终,上例中的网络模型被简化成了图4 的样子。
图 4
让我们从图4中重新总结一下构造这个网络模型的规则:
- 每个顾客分别用一个节点来表示。
- 对于每个猪圈的第一个顾客,从源点向他连一条边,容量就是该猪圈里的猪的初始数量。如果从源点到一名顾客有多条边,则可以把它们合并成一条,容量相加。
- 对于每个猪圈,假设有 n 个顾客打开过它,则对所有整数 i ∈ [1, n),从该猪圈的第 i 个顾客向第 i + 1 个顾客连一条边,容量为 +∞。
- 从各个顾客到汇点各有一条边,容量是各个顾客能买的数量上限。
拿我们前面一直在讲的例子来说:1 号猪圈的第一个顾客是 1 号顾客,所以从源点到 1 号顾客有一条容量为 3 的边;1 号猪圈的第二个顾客是 2 号顾客,因此从 1 号顾客到 2 号顾客有一条容量为 +∞ 的边;2 号猪圈的第一个顾客也是 1 号顾客,所以从源点到 1 号顾客有一条容量为 1 的边,和之前已有的一条边合并起来,容量变成 4;2 号猪圈的第二个顾客是 3 号顾客,因此从 1 号顾客到 3 号顾客有一条容量为 +∞ 的边;3 号猪圈的第一个顾客是 2 号顾客,所以从源点到 2 号顾客有一条容量为 10 的边。□
新的网络模型中最多只有 2 + N = 102 个节点,计算速度就可以相当快了。可以这样理解这个新的网络模型:对于某一个顾客,如果他打开了猪圈 h,则在他走后,他打开的所有猪圈里剩下的猪都有可能被换到 h 中,因而这些猪都有可能被 h 的下一个顾客买走。所以对于一个顾客打开的所有猪圈,从该顾客到各猪圈的下一个顾客,都要连一条容量为 +∞ 的边。
在面对网络流问题时,如果一时想不出很好的构图方法,不如先构造一个最直观,或者说最“硬来”的模型,然后再用合并节点和边的方法来简直化这个模型。经过简化以后,好的构图思路自然就会涌现出来了。这是解决网络流问题的一个好方法。
【代码】:
1 #include "iostream"
2 using namespace std;
3 #define maxn 500
4 #define maxm 50005
5 #define maxhouse 1005
6 const int INF = 99999999;
7 int s, t, house, ctm, tot;
8 int eh[maxn], pre[maxn], cur[maxn], gap[maxn], dist[maxn], a[maxn], hcap[maxhouse];
9 bool visit[maxhouse];
10
11 struct Edge {
12 int u, v, cap, flow, next;
13 }et[maxm];
14
15 void init() {
16 tot = 0;
17 memset(eh, -1, sizeof(eh));
18 }
19
20 int min(int a, int b) {
21 return a < b ? a : b;
22 }
23
24 void add(int u, int v, int cap, int flow) {
25 Edge E = {u, v, cap, flow, eh[u]};
26 et[tot] = E;
27 eh[u] = tot++;
28 }
29
30 void addedge(int u, int v, int cap) {
31 add(u, v, cap, 0), add(v, u, 0, 0);
32 }
33
34 int isap(int s, int t, int n) {
35 int u, v, now;
36 memset(dist, 0, sizeof(dist));
37 memset(a, 0, sizeof(a));
38 for(u = 0; u <= n; u++) cur[u] = eh[u];
39 int maxflow = 0;
40 u = s, a[s] = INF, gap[0] = n;
41 while(dist[s] < n) {
42 for(now = cur[u]; now != -1; now = et[now].next)
43 if(et[now].cap - et[now].flow && dist[u] == dist[v = et[now].v] + 1)
44 break;
45 if(now != -1) {
46 cur[u] = pre[v] = now;
47 a[v] = min(a[u], et[now].cap - et[now].flow);
48 u = v;
49 if(u == t) {
50 for(; u != s; u = et[pre[u]].u) {
51 et[pre[u]].flow += a[t];
52 et[pre[u]^1].flow -= a[t];
53 }
54 maxflow += a[t], a[s] = INF;
55 }
56 } else {
57 if(--gap[dist[u]] == 0) break;
58 dist[u] = n;
59 cur[u] = eh[u];
60 for(now = eh[u]; now != -1; now = et[now].next)
61 if(et[now].cap - et[now].flow && dist[u] > dist[et[now].v] + 1)
62 dist[u] = dist[et[now].v] + 1;
63 gap[dist[u]]++;
64 if(u != s) u = et[pre[u]].u;
65 }
66 }
67 return maxflow;
68 }
69
70 int main() {
71 int i, j, cap, v, nkey;
72 memset(visit, false, sizeof(visit));
73 cin >> house >> ctm;
74 init();
75 s = 0, t = ctm + 1;
76 for(i = 1; i <= house; i++) {
77 cin >> hcap[i];
78 }
79 for(i = 1; i <= ctm; i++) {
80 cin >> nkey;
81 for(j = 1; j <= nkey; j++) {
82 cin >> v;
83 if(!visit[v]) {
84 addedge(s, i, hcap[v]);
85 visit[v] = true;
86 pre[v] = i;
87 }
88 else addedge(pre[v], i, INF);
89 }
90 cin >> cap;
91 addedge(i, t, cap);
92 }
93 cout << isap(s, t, t - s + 1) << endl;
94 return 0;
95 }