「解题报告」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;
}
posted @ 2023-06-09 20:49  APJifengc  阅读(64)  评论(0编辑  收藏  举报