经典最小割模型三连击


小D的剑阵


题意

有 $n$ 个物品,选第 $i$ 个物品会得到 $w_i$ 的贡献。
有 $q$ 个约束,每个约束形如 $x$ $y$ $v_0$ $v_1$ $v_2$
$x$ 和 $y$ 表示两个物品编号,同时选中可以获得额外 $v_0$ 的贡献,同时不选可以获得额外 $v_1$ 的贡献,只选择一个会扣除 $v_2$ 的贡献。
问你怎样选能够获得最大贡献。
$w$ $v$ 都是偶数。

有约束存在,考虑用网络流,这就是一个经典的最小割模型,把全部有的贡献加起来,然后选矛盾最小的贡献割掉就是答案了。

每一组矛盾就是一个割,把这些矛盾的式子列出来然后建图。

假设连 $s$ 表示不选,$t$ 表示选。

$a + c = w_x + w_y + v_0$ (都不选,损失掉两个原基础贡献和 $v_0$)
$b + d = v_1$ (都选,损失掉都不选的贡献 $v_1$)
$a + e + d = w_x + v_0 + v_1 + v_2$ (选 $y$ 不选 $x$ 损失掉 $w_x$ 和所有 $v$)
$c + f + b = w_y + v_0 + v_1 + v_2$ (选 $x$ 不选 $y$ 损失掉 $w_y$ 和所有 $v$)

发现化出来有个 $e + f = v_0 + v_1 + 2 \times v_2$。

这个与 $w$ 无关。

所以

$e = f = \frac{v_0 + v_1}{2} + v_2$
$b = d = \frac{v_1}{2}$
$a = w_x + \frac{v_0}{2}$
$c = w_y + \frac{v_0}{2}$

答案就是把所有 $w_i$,所有约束的 $v_0$ $v_1$ 加起来,再减去最小割。

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <bits/stdc++.h>
#include <ext/rope>
#include <algorithm>
using namespace __gnu_cxx;
using namespace std;
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define pii pair<int, int>
#define debug(a) cout << #a": " << a << endl;
#define eularMod(a, b) a < b ? a : a % b + b
#define X first
#define Y second
#define lc rt << 1
#define rc rt << 1 | 1
inline int lowbit(int x){ return x & -x; }
typedef long long LL;
typedef unsigned long long ULL;
const int N = 1e4 + 10;
const int M = 2e5 + 10;
const int mod = (int) 1e9 + 7;
const int INF = 0x3f3f3f3f;
const long long LINF = 0x3f3f3f3f3f3f3f3fLL;
const double PI = acos(-1.0);
const double eps = 1e-6;

struct Edge {
int to, nex;
LL cap, flow;
}edge[M];
int head[N], tol;
LL w[N];
LL W[N];

void init() {
tol = 2;
mst(head, -1);
}
void addEdge(int u, int v, LL w, LL rw = 0) {
edge[tol] = {v, head[u], w, 0};
head[u] = tol++;
}
int Q[M];
int dep[M], cur[M], sta[M];
bool bfs(int s, int t, int n) {
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n + 1));
dep[s] = 0;
Q[tail++] = s;
for (;front < tail;) {
int u = Q[front++];
for (int i = head[u]; ~i; i = edge[i].nex) {
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1) {
dep[v] = dep[u] + 1;
if (v == t)
return true;
Q[tail++] = v;
}
}
}
return false;
}
LL dinic(int s, int t, int n) {
LL maxflow = 0;
for (;bfs(s, t, n);) {
for (int i = 0; i < n; i++)
cur[i] = head[i];
int u = s, tail = 0;
for (;cur[s] != -1;) {
if (u == t) {
LL tp = LINF;
for (int i = tail - 1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
maxflow += tp;
for (int i = tail - 1; i >= 0; i--) {
edge[sta[i]].flow += tp;
edge[sta[i] ^ 1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0)
tail = i;
}
u = edge[sta[tail] ^ 1].to;
} else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow && dep[u] + 1 == dep[edge[cur[u]].to]) {
sta[tail++] = cur[u];
u = edge[cur[u]].to;
} else {
for (;u != s && cur[u] == -1;)
u = edge[sta[--tail] ^ 1].to;
cur[u] = edge[cur[u]].nex;
}
}
}
return maxflow;
}
int main() {
#ifdef purple_bro
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
int n, q;
LL sum = 0;

init();

scanf("%d%d", &n, &q);

for (int i = 1; i <= n; i++) {
scanf("%lld", &w[i]);
sum += w[i];
}

int S = 0, T = n + 1;

for (int i = 1; i <= q; i++) {
int x, y, v0, v1, v2;
scanf("%d%d%d%d%d", &x, &y, &v0, &v1, &v2);
sum += v0 + v1;
W[x] += v1 / 2;
W[y] += v1 / 2;
w[x] += v0 / 2;
w[y] += v0 / 2;
addEdge(x, y, (v0 + v1) / 2 + v2);
addEdge(y, x, (v0 + v1) / 2 + v2);
}

for (int i = 1; i <= n; i++) {
addEdge(S, i, w[i]);
addEdge(i, S, 0);
addEdge(i, T, W[i]);
addEdge(T, i, W[i]);
}

printf("%lld\n", sum - dinic(S, T, T + 1));

return 0;
}

文理分科


题意

文理分科是一件很纠结的事情!(虽然看到这个题目的人肯定都没有纠
结过)
小$P$所在的班级要进行文理分科。他的班级可以用一个$n*m$的矩阵进行
描述,每个格子代表一个同学的座位。每位同学必须从文科和理科中选择
一科。同学们在选择科目的时候会获得一个满意值。满意值按如下的方式
得到:
1.如果第$i$行第$j$列的同学选择了文科,则他将获得$art[i][j]$的满意值,如
果选择理科,将得到$science[i][j]$的满意值。
2.如果第$i$行第$j$列的同学选择了文科,并且他相邻(两个格子相邻当且
仅当它们拥有一条相同的边)的同学全部选择了文科,则他会更开
心,所以会增加$same_art[i][j]$的满意值。
3.如果第$i$行第$j$列的同学选择了理科,并且他相邻的同学全部选择了理
科,则增加$same_science[i][j]$的满意值。
小$P$想知道,大家应该如何选择,才能使所有人的满意值之和最大。请
告诉他这个最大值。

也是一道把所有贡献加起来,把最小矛盾割掉求贡献的题。

建图类似上一题,只不过这题的约束是多个点。

连 $s$ 表示选文,连 $t$ 表示选理。

我们考虑每个点拆两个出来,一个连 $s$ 和四周围的人的点,另一个连 $t$ 和四周围的人的点。

差不多是下图这个样子。

左边连 $s$ 的边容量为 $art$,右边连 $t$ 的边容量为 $science$,左边连 $s$ 的出来的点的边容量为 $art_same$,右边连 $t$ 的出来的点的边容量为 $science_same$,红色的边容量为 $inf$。

答案是全部贡献 - 最小割。

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <bits/stdc++.h>
#include <ext/rope>
#include <algorithm>
using namespace __gnu_cxx;
using namespace std;
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define pii pair<int, int>
#define debug(a) cout << #a": " << a << endl;
#define eularMod(a, b) a < b ? a : a % b + b
#define X first
#define Y second
#define lc rt << 1
#define rc rt << 1 | 1
inline int lowbit(int x){ return x & -x; }
typedef long long LL;
typedef unsigned long long ULL;
const int N = 105 + 10;
const int M = 1e4 + 10;
const int mod = (int) 1e9 + 7;
const int INF = 0x3f3f3f3f;
const long long LINF = 0x3f3f3f3f3f3f3f3fLL;
const double PI = acos(-1.0);
const double eps = 1e-6;

int a[N][N];
int s[N][N];
int as[N][N];
int ss[N][N];
int tag[N][N];
int dx[] = {0, 0, 0, -1, 1};
int dy[] = {0, -1, 1, 0, 0};

struct Edge {
int to, nex, cap, flow;
}edge[M << 5];
int head[M << 4], tol;

void init() {
tol = 2;
mst(head, -1);
}
void addEdge(int u, int v, int w, int rw = 0) {
edge[tol] = {v, head[u], w, 0};
head[u] = tol++;
edge[tol] = {u, head[v], rw, 0};
head[v] = tol++;
}
int Q[M << 4];
int dep[M << 4], cur[M << 4], sta[M << 4];
bool bfs(int s, int t, int n) {
int front = 0, tail = 0;
memset(dep, -1, sizeof(dep[0]) * (n + 1));
dep[s] = 0;
Q[tail++] = s;
for (;front < tail;) {
int u = Q[front++];
for (int i = head[u]; ~i; i = edge[i].nex) {
int v = edge[i].to;
if (edge[i].cap > edge[i].flow && dep[v] == -1) {
dep[v] = dep[u] + 1;
if (v == t)
return true;
Q[tail++] = v;
}
}
}
return false;
}
int dinic(int s, int t, int n) {
int maxflow = 0;
for (;bfs(s, t, n);) {
for (int i = 0; i < n; i++)
cur[i] = head[i];
int u = s, tail = 0;
for (;cur[s] != -1;) {
if (u == t) {
int tp = INF;
for (int i = tail - 1; i >= 0; i--)
tp = min(tp, edge[sta[i]].cap - edge[sta[i]].flow);
maxflow += tp;
for (int i = tail - 1; i >= 0; i--) {
edge[sta[i]].flow += tp;
edge[sta[i] ^ 1].flow -= tp;
if (edge[sta[i]].cap - edge[sta[i]].flow == 0)
tail = i;
}
u = edge[sta[tail] ^ 1].to;
} else if (cur[u] != -1 && edge[cur[u]].cap > edge[cur[u]].flow && dep[u] + 1 == dep[edge[cur[u]].to]) {
sta[tail++] = cur[u];
u = edge[cur[u]].to;
} else {
for (;u != s && cur[u] == -1;)
u = edge[sta[--tail] ^ 1].to;
cur[u] = edge[cur[u]].nex;
}
}
}
return maxflow;
}
int main() {
#ifdef purple_bro
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
int n, m;
int tot = 0;
int sum = 0;

init();

scanf("%d%d", &n, &m);

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
scanf("%d", &s[i][j]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
scanf("%d", &as[i][j]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
scanf("%d", &ss[i][j]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
tag[i][j] = ++tot;
sum += a[i][j] + s[i][j] + as[i][j] + ss[i][j];
}
}

int S = 0, T = 3 * tot + 1;

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
addEdge(S, tag[i][j], a[i][j]);
addEdge(tag[i][j], T, s[i][j]);
addEdge(S, tag[i][j] + tot, as[i][j]);
addEdge(tag[i][j] + tot * 2, T, ss[i][j]);
for (int k = 0; k <= 4; k++) {
int toX = i + dx[k];
int toY = j + dy[k];
if (toX > 0 && toX <= n && toY > 0 && toY <= m) {
addEdge(tag[i][j] + tot, tag[toX][toY], INF);
addEdge(tag[toX][toY], tag[i][j] + tot * 2, INF);
}
}
}
}

printf("%d\n", sum - dinic(S, T, T + 1));

return 0;
}

最后一连击,直接丢 $blog$