「解题报告」CF1662J Training Camp
模拟赛题,数据水被 dfs 草过去了。
我们可以把每个点分成两个点 \(a_{i, j}, b_{i, j}\),设这一行中选取的数为 \(v\),那么对于一行内 \(\ge v\) 的点选 \(a\),大于 \(v\) 的点选 \(b\),那么题目的限制相当于每个点只能够选一个颜色。
看起来就像网络流,考虑怎么转化到图上去。
我们发现这样选择的两端一定是连续的一段,即 \(a\) 一定是连续的两端,且交于选取的那点,所以我们考虑一种建图:每一行将权值为 \(i \to i+1\) 连边,然后 \(s \to 1, n \to t\),每一列同理。这样,我们要选择的的点对应着 \(s\) 向右,然后从某个点向下转,然后到 \(t\) 的一条路径。我们拆点转化成最小割模型。但是这样并不好限制必须转一次的限制。怎么办?
实际上可以证明,这个限制直接去掉也是对的,即直接按上面的方式建图跑最小割,钦定恰好割 \(n\) 个点,一定能够对应一种合法方案。
考虑证明。首先证明这样的割一定对应一种合法方案。考虑反证法,假如存在某个 \((x_1, y_1) \to (x_1, y_2) \to (x_2, y_2)\),满足割中割掉了 \((x_1, y_1), (x_2, y_2)\),且 \(a_{x_1, y_1} < a_{x_1, y_2} < a_{x_2, y_2}\),那么我们一定能在同一行同一列中找到 另外两个点 \((x_1, y_3), (x_3, y_2)\) 满足 \(a_{x_1, y_3} > a_{x_1, y_2} > a_{x_3, y_2}\),而这样的路径一定联通,与割矛盾。
然后证明一种合法方案一定对应着一组割。假如存在一组合法方案,仍然有一条 \(A_1 \to A_2 \to \cdots \to A_n\) 的路径使得图联通,那么首先可以得到 \(A_1\) 所在的行 / 列删除的权值一定大于 \(1\),那么相对应的 \(A_2\) 与 \(A_1\) 所在的同一行 / 列删除的权值一定会大于 \(2\)。这样归纳下去,最后能够得到 \(A_n\) 所在的行 / 列选择的权值大于 \(n\),这显然是不合法的。所以证明了这种做法的正确性。
然后直接建图跑最小割即可。需要把权值 \(01\) 翻转过来。注意我们有恰好选 \(n\) 个的限制,而少于 \(n\) 个一定无法构成割,所以我们给每一条边加一个很大的权值,这样最小割一定得到的是割边最少的方案。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 130, MAXP = 17005 * 2, MAXM = MAXP * 4;
const long long inf = 0x3f3f3f3f3f3f3f3f;
int n, a[MAXN][MAXN];
int b[MAXN][MAXN];
struct Graph {
int fst[MAXP], nxt[MAXM], to[MAXM], tot;
int now[MAXP], d[MAXP];
long long f[MAXM];
int s, t;
void init() {
memset(fst, 0, sizeof fst);
s = t = 0;
tot = 1;
}
void add(int u, int v, long long w) {
to[++tot] = v, nxt[tot] = fst[u], fst[u] = tot, f[tot] = w;
to[++tot] = u, nxt[tot] = fst[v], fst[v] = tot, f[tot] = 0;
}
bool bfs() {
memset(d, 0, sizeof d);
memcpy(now, fst, sizeof fst);
queue<int> q; q.push(s); d[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = fst[u], v = to[i]; i; i = nxt[i], v = to[i]) if (f[i] && !d[v]) {
d[v] = d[u] + 1;
q.push(v);
}
}
return d[t];
}
long long dfs(int u, long long flow) {
if (u == t) return flow;
long long rest = flow;
for (int &i = now[u], v = to[i]; i; i = nxt[i], v = to[i]) {
if (f[i] && d[v] == d[u] + 1) {
long long k = dfs(v, min(rest, f[i]));
if (!k) d[v] = 0;
rest -= k, f[i] -= k, f[i ^ 1] += k;
}
if (!rest) break;
}
return flow - rest;
}
long long dinic() {
long long flow, maxflow = 0;
while (bfs()) while (flow = dfs(s, inf)) maxflow += flow;
return maxflow;
}
} g;
int id[MAXN][MAXN][2];
int r[MAXN][MAXN], c[MAXN][MAXN];
int tot;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &b[i][j]);
}
}
g.init();
g.s = ++tot, g.t = ++tot;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
id[i][j][0] = ++tot, id[i][j][1] = ++tot;
g.add(id[i][j][0], id[i][j][1], 1 - b[i][j] + 2 * n);
r[i][a[i][j]] = j, c[j][a[i][j]] = i;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (a[i][j] == 1) g.add(g.s, id[i][j][0], inf);
if (a[i][j] == n) g.add(id[i][j][1], g.t, inf);
else g.add(id[i][j][1], id[i][r[i][a[i][j] + 1]][0], inf), g.add(id[i][j][1], id[c[j][a[i][j] + 1]][j][0], inf);
}
}
printf("%lld\n", n - (g.dinic() - 2 * n * n));
return 0;
}