题目分析

如果我们将要求拍照的人和下属之间按照要求关系连有向边,我们就会发现题目实际上是要求我们求出有向图的最大权闭合子图。即,在一张有向图上选择一个点集,要求点集中的每一个点的后继也都在这个点集中,使点集中点权和最大。

对于这类问题,有一个结论:从源点向所有正权点连边,边权为原点权;从所有负权点向汇点连边,边权为原点权的绝对值;原图中的边权设为正无穷。则最大权闭合子图的权值和 = 所有正权点的权值和 - 新图的最小割。

对结论的证明

我们先做如下约定:

  1. 割掉源点与正权点 \(u\) 之间的边,表示不选择点 \(u\) 进入子图。
  2. 割掉负权点 \(v\) 与汇点之间的边,表示选择点 \(v\) 进入子图。

则我们跑一遍最小割后得到的一定是闭合子图,证明如下:考虑任意得到的子图内的任意正权点 \(u\),若存在其后继节点 \(v\) 没有被选择进入子图,分为两种情况:如果 \(v\) 权值为负,按照上述约定,点 \(v\) 与汇点间的边会被保留,源点与汇点联通,与最小割的定义矛盾;如果 \(v\) 权值为正,按照上述约定,源点与点 \(v\) 间的边会被割掉,但点 \(u\) 与点 \(v\) 联通,不割掉源点与点 \(v\) 间的边仍然是一个割,且权值和更小,与最小割的定义矛盾。

因为最大权闭合子图的权值和为:

被选的正权点权值和 - 被选的负权点权值和的绝对值

等价于

所有正权点的权值和 - (不被选的正权点的权值和 + 被选的负权点权值和的绝对值)

而根据先前的约定,我们可以得到:

最小割 = 不被选的正权点的权值和 + 被选的负权点权值和的绝对值

所以

最大权闭合子图的权值和 = 所有正权点的权值和 - 新图的最小割

代码实现

因为最大流=最小割,所以跑一边最大流即可。笔者在这里选择使用 Dinic 算法来实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 200 + 5;
const int MAXM = 1000000 + 5;
const int INF = INT_MAX;

queue<int> q;
int head[MAXN], to[MAXM], nxt[MAXM], flow[MAXM], cap[MAXM];
int tot = 1;
int dep[MAXN], cur[MAXN];
int m, n, s, t;

void addEdge(int u, int v, int c, int f) {
tot++;
to[tot] = v;
nxt[tot] = head[u];
flow[tot] = f;
cap[tot] = c;
head[u] = tot;
}

bool bfs() {
memset(dep, 0, sizeof(dep));
while(!q.empty()) q.pop();
q.push(s);
dep[s] = 1;
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = head[u]; i != 0; i = nxt[i]) {
if(cap[i] > flow[i] && dep[to[i]] == 0) {
dep[to[i]] = dep[u] + 1;
q.push(to[i]);
if(to[i] == t) return true;
}
}
}
return false;
}

int dinic(int u, int f) {
if(u == t || f == 0) {
return f;
}
int rest = f;
for(int& i = cur[u]; i != 0; i = nxt[i]) {
if(cap[i] > flow[i] && dep[to[i]] == dep[u] + 1) {
int k = dinic(to[i], min(rest, cap[i] - flow[i]));
if(k == 0) {
dep[to[i]] = 0;
}
flow[i] += k;
flow[i ^ 1] -= k;
rest -= k;
if(rest == 0) break;
}
}
return f - rest;
}

int main() {
scanf("%d%d", &m, &n);
s = m + n + 1;
t = m + n + 2;
int sum = 0;
for(int i = 1; i <= m; i++) {
int v, p;

scanf("%d", &v);
sum += v;
addEdge(s, i, v, 0);
addEdge(i, s, 0, 0);
while(1) {
scanf("%d", &p);
if(p == 0) break;
addEdge(i, p + m, INF, 0);
addEdge(p + m, i, 0, 0);
}
}
for(int i = 1; i <= n; i++) {
int v;

scanf("%d", &v);
addEdge(i + m, t, v, 0);
addEdge(t, i + m, 0, 0);
}
int maxflow = 0;
while(bfs()) {
memcpy(cur, head, sizeof(head));
maxflow += dinic(s, INF);
}
printf("%d\n", sum - maxflow);

return 0;
}