8.22 [CSP-S 2021] 交通规划 题解

bingxin-ly / 2023-08-23 / 原文

此题出处为 CSP-S 2021 T4,具有相当的水准与价值。

题意较清晰,为求一张网格图的最小割。
\(k = 2\) 时,弱化为经典“狼抓兔子”,平面图最小割转对偶图最短路。对偶图的每条边与其对偶边权值相等,两关键点之间的最短路即为所求。
\(k > 2\) 时,网格图之外的关键点数量增多了,原问题得到强化,类似于 \(k = 2\) 时的做法,假设我们现在已经完成对偶图的转化,原本的的网格外面会多出 \(k\) 个关键点。设 \(L_i\) 为第 \(i\) 个点向逆时针方向看看到的第一条射线的颜色,\(R_i\) 同理为顺时针方向。我们可以先对这几个点进行一波操作。注意到,当 \(L_i = R_i\) 时,可贪心地直接忽略这个点,因为它两边的射线颜色一致,在中间进行划分只会让答案不优。这样操作之后,余下的点的 \((L_i, R_i)\) 一定是 \((0, 1)\)\((1, 0)\) 交替出现的一个环,我们在两两点之间连线,路径经过的所有边颜色不同,每一种匹配方案都对应一种划分方案。
还是很困难?注意到性质:最优划分中任意两点的匹配不会相交,考虑巴拉巴拉,答案一定不会更劣,然后就能类似于括号匹配那样区间 DP。这个转化和 [春季测试 2023] 圣诞树 有异曲同工之妙啊(赞赏),都是得出连线不会相交的性质然后区间 DP,那道题更弱,可以试着去切一下。然后这道题就没了,另外题外话,我觉得这道题紫中中上,狼抓兔子是 BZOJ 第一道题,也是评紫的一道题,紫 + 蓝 + 大码量 = 紫。洛谷你评级真就生辰八字随一个是吧。

#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

constexpr int N = 3e5 + 5, S = 2e3 + 5, K = 1e2 + 5, INF = 0x3f3f3f3f;
int n, m, T, poi[N];
int hed[N], nxt[N << 2], rch[N << 2], val[N << 2], idx;
void link(int u, int v, int w)
{
    nxt[++idx] = hed[u], hed[u] = idx, rch[idx] = v, val[idx] = w;
    nxt[++idx] = hed[v], hed[v] = idx, rch[idx] = u, val[idx] = w;
}

int trs(int i, int j) { return (m + 1) * i + j; }
signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m >> T;
    for (int i = 1, w, p; i < n; i++)
        for (int j = 1; j <= m; j++)
            p = trs(i, j - 1), cin >> w, link(p, p + 1, w);
    for (int i = 1, w, p; i <= n; i++)
        for (int j = 1; j < m; j++)
            p = trs(i - 1, j), cin >> w, link(p, p + m + 1, w);
    for (int i = 1; i <= (n + m) << 1; i++)
        if (i <= m)
            poi[i] = trs(0, i - 1);
        else if (i <= m + n)
            poi[i] = trs(i - m - 1, m);
        else if (i <= 2 * m + n)
            poi[i] = trs(n, 2 * m + n + 1 - i);
        else
            poi[i] = trs(2 * m + 2 * n + 1 - i, 0);
    const int ori = idx;

    while (T--)
    {
        for (int i = 0; i < (n + 1) * (m + 1); i++)
            while (hed[i] > ori)
                hed[i] = nxt[hed[i]];
        idx = ori;

        static int col[S], cst[S], k;
        memset(col, -1, sizeof(col)), memset(cst, 0, sizeof(cst)), cin >> k;
        for (int i = 1, w, p, c; i <= k; i++)
            cin >> w >> p >> c, col[p] = c, cst[p] = w;
        for (int i = 1; i <= (n + m) << 1; i++)
            if (i <= m)
                link(poi[i], poi[i] + 1, cst[i]);
            else if (i <= m + n)
                link(poi[i], poi[i] + m + 1, cst[i]);
            else if (i <= 2 * m + n)
                link(poi[i], poi[i] - 1, cst[i]);
            else
                link(poi[i], poi[i] - m - 1, cst[i]);

        vector<vector<int>> outer;
        int fir = 0, lst = 0;
        for (int i = 1; i <= (n + m) << 1; i++)
        {
            if (col[i] == -1)
                continue;
            if (!fir)
                fir = i;
            else if (col[lst] != col[i])
            {
                vector<int> inner;
                for (int j = lst + 1; j <= i; j++)
                    inner.emplace_back(poi[j]);
                outer.emplace_back(inner);
            }
            lst = i;
        }
        if (col[lst] != col[fir])
        {
            vector<int> inner;
            for (int i = lst + 1; i <= (n + m) << 1; i++)
                inner.emplace_back(poi[i]);
            for (int i = 1; i <= fir; i++)
                inner.emplace_back(poi[i]);
            outer.emplace_back(inner);
        }
        if (outer.empty())
        {
            cout << "0\n";
            continue;
        }

        static int w[K][K], f[K][K];
        memset(w, 0x3f, sizeof(w)), memset(f, 0x3f, sizeof(f)), k = outer.size();
        for (int i = 0; i < k; i += 2)
        {
            static int dis[N];
            memset(dis, 0x3f, sizeof(dis));
            priority_queue<pii, vector<pii>, greater<>> q;
            for (int u : outer[i])
                q.emplace(dis[u] = 0, u);
            while (!q.empty())
            {
                int pre = q.top().first, u = q.top().second;
                q.pop();
                if (pre != dis[u]) continue;
                for (int e = hed[u]; e; e = nxt[e])
                {
                    int v = rch[e], cur = pre + val[e];
                    if (dis[v] > cur) q.emplace(dis[v] = cur, v);
                }
            }
            for (int j = 0; j < k; j++)
            {
                for (int u : outer[j])
                    w[i][j] = min(w[i][j], dis[u]);
                w[j][i] = w[i][j];
            }
        }
        for (int i = 0; i < k; i++)
            for (int j = 0; j < k; j++)
                w[i + k][j] = w[i][j + k] = w[i + k][j + k] = w[i][j];
        for (int i = 1; i <= k << 1; i++)
            f[i][i - 1] = 0;
        for (int i = 2; i <= k; i++)
            for (int l = 1; l <= (k << 1) - i + 1; l++)
            {
                int r = l + i - 1;
                f[l][r] = f[l + 1][r - 1] + w[l][r];
                for (int x = l + 1; x < r; x += 2)
                    f[l][r] = min(f[l][r], f[l][x] + f[x + 1][r]);
            }
        int ans = INF;
        for (int i = 0; i < k; i++)
            ans = min(ans, f[i][i + k - 1]);
        cout << ans << '\n';
    }
    return 0;
}