1 条题解

  • 0
    @ 2026-10-27 8:00:08

    思路与算法

    先考虑s到t的树上简单路径。权值全为正,最优转发链可按其在此路径的投影顺序前进。k≤2时只需选路径点;k=3时还可能选某路径点旁一条边处的便宜点。记v_i为路径点权,c_i为它所有邻点的最小权。路径邻点也可纳入c_i,三角不等式保证这种保守距离模型生成的转发仍合法,且真正旁支候选全部包含。 沿路径维护三个状态:最后一个所选转发点距当前前沿为0、1、2时的最小代价。前沿从i-1移到i,k=3转移为 f'_0=v_i+min(f_0,f_1,f_2),f'_1=min(f_0,c_i+f_1),f'_2=f_1。也就是min-plus矩阵[[v,v,v],[0,c,∞],[∞,0,∞]]乘状态列向量。选旁支点只有在旧距离≤1时合法;旧距离0的旁支选择被不选任何新点的f_0支配,故只保留c_i+f_1。k=2截取左上2×2、k=1只剩[v]。 树链剖分把查询路径拆成有方向的连续编号区间。线段树同时保存各区间从左向右和从右向左的矩阵积。查询初始化为(v_s,∞,∞),跳过首点矩阵,依路径顺序对状态应用各区间矩阵,最终取状态0,强制t也被选。

    正确性说明

    先规范化转发链。对于连接在主路径点p上的一个旁支,取链进入该旁支到最后离开的整段。两端在旁支外,若入口或出口使用的旁支点深度至少2,k≤3使其外侧点到p距离至多1,另一侧至多2,外侧两点可以直接相连并删掉这次旁支绕行。否则入口和出口都只能是p的同一直接邻点,保留它一次、删除中间绕行即可。因此深度至少2的旁支点全部可去掉,k≤2时连深度1旁支也不需要。 余下点到主路径的距离只能为0或1。若三个连续投影形成严格局部极大或极小,中间点到两侧的整数间距至少1,足以补偿两侧离路径高度差至多1,所以两侧距离不大于原来两条边的最大值,可以直接相连并删中间点。同投影的连续段先去掉多余点:有路径点时它到外部总不比旁支点远,没有路径点时保留最低费用旁支点。于是反复消去投影回退,得到按s至t投影单调的链,代价不增。 对这样的规范链,选路径点时旧距离加1≤k,选旁支时旧距离加2≤3,正好是转移列出的选择。保留到前沿距离1的旧路径点不付新代价,支配从距离0旧状态另选旁支的方案,删去这条被支配转移不影响最优。归纳三状态涵盖所有规范链且每个转移都可实现。端点初始化和取状态0保证两端费用都计入。min-plus乘法满足结合律,区间矩阵依次应用与逐点DP相同;双向矩阵与树链区间拼接恰按s至t次序,故得到全局最小转发代价。

    复杂度

    扫描邻边求c、迭代求父子序与重链编号O(n),建线段树O(nk³),空间O(nk²+n)。一条树路径拆O(log n)重链,每段访问O(log n)线段树结点,对状态的应用O(k²),每询问O(k²log²n),总O(nk³+Qk²log²n),k≤3。没有深树递归遍历,线段树也用迭代构建与查询。

    易错点

    k=3只在主路径做三步DP会漏掉便宜旁支。矩阵乘法不交换,反向区间不能沿正序使用。终点必须作为转发点计费;距离是树的边数,单点处理时间是点权。总答案可达2×10^14,需64位。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const ll INF = 4000000000000000000LL;
    int K;
    struct Mat {
        ll a[3][3];
        Mat(bool identity = false) {
            for (int i = 0; i < 3; i++)
                for (int j = 0; j < 3; j++)
                    a[i][j] = (identity && i == j ? 0 : INF);
        }
    };
    Mat mul(const Mat &a, const Mat &b) {
        Mat z;
        for (int i = 0; i < K; i++)
            for (int j = 0; j < K; j++)
                for (int t = 0; t < K; t++)
                    z.a[i][j] = min(z.a[i][j], a.a[i][t] + b.a[t][j]);
        return z;
    }
    void apply(const Mat &a, array<ll, 3> &v) {
        array<ll, 3> w = {INF, INF, INF};
        for (int i = 0; i < K; i++)
            for (int j = 0; j < K; j++)
                w[i] = min(w[i], a.a[i][j] + v[j]);
        v = w;
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n, q;
        cin >> n >> q >> K;
        vector<ll> value(n + 1), cheap(n + 1, INF);
        for (int i = 1; i <= n; i++)
            cin >> value[i];
        vector<vector<int>> adj(n + 1);
        for (int i = 1, u, v; i < n; i++) {
            cin >> u >> v;
            adj[u].push_back(v);
            adj[v].push_back(u);
            cheap[u] = min(cheap[u], value[v]);
            cheap[v] = min(cheap[v], value[u]);
        }
        vector<int> parent(n + 1), depth(n + 1), size(n + 1, 1), heavy(n + 1), order{1};
        size[0] = 0;
        for (size_t i = 0; i < order.size(); i++) {
            int u = order[i];
            for (int v : adj[u])
                if (v != parent[u]) {
                    parent[v] = u;
                    depth[v] = depth[u] + 1;
                    order.push_back(v);
                }
        }
        for (int i = n - 1; i > 0; i--) {
            int u = order[i], p = parent[u];
            size[p] += size[u];
            if (size[u] > size[heavy[p]])
                heavy[p] = u;
        }
        vector<int> head(n + 1), pos(n + 1), at(n);
        int timer = 0;
        vector<pair<int, int>> todo{{1, 1}};
        while (!todo.empty()) {
            auto a = todo.back();
            todo.pop_back();
            for (int u = a.first; u; u = heavy[u]) {
                head[u] = a.second;
                pos[u] = timer;
                at[timer++] = u;
                for (int v : adj[u])
                    if (parent[v] == u && v != heavy[u])
                        todo.push_back({v, v});
            }
        }
        int S = 1;
        while (S < n)
            S *= 2;
        vector<Mat> f(2 * S, Mat(true)), b(2 * S, Mat(true));
        for (int i = 0; i < n; i++) {
            int u = at[i];
            Mat m;
            for (int j = 0; j < K; j++)
                m.a[0][j] = value[u];
            for (int j = 1; j < K; j++)
                m.a[j][j - 1] = 0;
            if (K == 3)
                m.a[1][1] = cheap[u];
            f[S + i] = b[S + i] = m;
        }
        for (int i = S - 1; i; i--) {
            f[i] = mul(f[i * 2 + 1], f[i * 2]);
            b[i] = mul(b[i * 2], b[i * 2 + 1]);
        }
        auto rangeApply = [&](int l, int r, bool reverse, array<ll, 3> &state) {
            vector<int> left, right;
            for (l += S, r += S + 1; l < r; l /= 2, r /= 2) {
                if (l & 1)
                    left.push_back(l++);
                if (r & 1)
                    right.push_back(--r);
            }
            if (!reverse) {
                for (int x : left)
                    apply(f[x], state);
                for (auto it = right.rbegin(); it != right.rend(); it++)
                    apply(f[*it], state);
            } else {
                for (int x : right)
                    apply(b[x], state);
                for (auto it = left.rbegin(); it != left.rend(); it++)
                    apply(b[*it], state);
            }
        };
        while (q--) {
            int s, t;
            cin >> s >> t;
            array<ll, 3> state = {value[s], INF, INF};
            bool first = true;
            auto go = [&](int l, int r, bool reverse) {
                if (first) {
                    if (reverse)
                        --r;
                    else
                        ++l;
                    first = false;
                }
                if (l <= r)
                    rangeApply(l, r, reverse, state);
            };
            vector<pair<int, int>> tail;
            int u = s, v = t;
            while (head[u] != head[v]) {
                if (depth[head[u]] >= depth[head[v]]) {
                    go(pos[head[u]], pos[u], true);
                    u = parent[head[u]];
                } else {
                    tail.push_back({pos[head[v]], pos[v]});
                    v = parent[head[v]];
                }
            }
            if (depth[u] >= depth[v])
                go(pos[v], pos[u], true);
            else
                go(pos[u], pos[v], false);
            for (auto it = tail.rbegin(); it != tail.rend(); it++)
                go(it->first, it->second, false);
            cout << state[0] << '\n';
        }
    }
    
    • 1