「ROI 2021」旅行

图论综合好题。

首先如果没有 \(+ \min\limits_{1 \leq i \leq q - 1} {t_{r_i}}\) 的话就最小生成树秒了,所以一开始可以想到最小生成树相关,但没啥头绪,先搁一边。

现在想到难处理的是路径取 \(\min\) 操作,所以先来思考怎么处理这坨东西。由于原题要求路径不能重复走过同一条边,这个东西在可以绕路去拿最小值的情况下不太好刻画,所以考虑有没有什么东西可以让我们要么可以不管这个限制条件,要么这条边就只能单向走,老油条就能联系到边双,因为边双有一个很好的性质:任意两个点 \(u, v\) 一定存在至少两条路径满足边集没有交集。

简要证明 以 $u$ 为源点,$v$ 为汇点,将每条边流量设为 $1$,由边双定义,割掉任何一条边 $u, v$ 仍然联通,所以该图最小割 $> 1$,由最大流最小割定理可得该图最大流 $> 1$,而在该图中最大流每个流量流经边边集没有交集,所以 $u, v$ 存在至少两条路径边集没有交集。

所以如果没在边双中的边从 \(1\) 出发就只能直接经过,而在边双中的边从 \(1\) 出发前往某个点若经过该边双中任意一个点就一定可以在合法(不一定最优)的情况下经过这条边且不影响原方案。那不最优有什么用呢?这时需要去观察为什么会导致不优,显然是因为边权最大值可能会边大,所以想到按边权从小到大将边加入图,并将边双缩点,令新点点权为该点双内所有边权最小值,那从 \(1\)\(u\) 路径上的边权最小值 \(mn\) 就等于 \(1\)\(u\) 在边双缩点后的路径上边权与点权的最小值,设在最大边权 \(\leq W\) 的情况下从 \(1\)\(u\) 的最小边权为 \(mn_W\),则答案为 \(\min\limits_{W} {W + mn_W}\),时间复杂度 \(O(NM + N^2)\)

这肯定不能过啊,考虑优化,我们发现按边权从小到大每次将边 \((u, v, w)\) 加入图中并不需要对整个图重新进行边双缩点,我们只对该边涉及到的边双进行操作,分 \(3\) 种情况。

  • \(u, v\) 在同一个边双中,不做任何操作
  • \(u, v\) 不连通,连通 \(u, v\)
  • \(u, v\) 连通但不在同一个边双中,将 \(u\) 所在边双与 \(v\) 所在边双合并为一个边双(正确性易证)

我们发现只有第二类操作会对图的连通性造成影响,而这个操作又与 kruskal 的流程一模一样,所以考虑将边双缩点放到最小生成树的形态上考虑,就可以应用 dfs 序对一些点进行批量操作。

发现第 \(3\) 类边对应到最小生成树上就是把 \(u, v\) 路径上的所有点缩成一个点并赋上边权,这可以用并查集维护缩点,均摊 \(O(N \log N)\)。考虑怎么更新答案。对于每次枚举边 \(e\) 时缩点,设新点的点权为 \(val\),设新点所代表的点中最接近根的点为 \(rt\),那么对于所有 \(rt\) 子树内的点都要 chkmn \(e_w + val\),该操作区间修改单点查询,线段树解决。

但我们发现缩点时若 \(rt\) 子树内有点不与 \(1\) 连通这次 chkmn 就是非法的,所以对于每个点 \(u\) 第一次与 \(1\) 连通时还要将之前的 chkmn 操作作废,并赋上此时从 \(u\)\(1\) 路径上的边权与点权最小值 \(e_w\),所以另开一棵线段树维护从 \(u\) 到根 \(1\) 路径上的边权点权最小值,均摊 \(O(N \log N)\)

综上,总时间复杂度 \(O(N \log N)\),空间复杂度 \(O(N)\)

/*
address:https://www.luogu.com.cn/problem/P11095
AC 2026/9/3 20:44
*/
#include<bits/stdc++.h>
using namespace std;
typedef unsigned int ui;
typedef pair<int, ui> piu;
#define mkp make_pair
const int N = 3e5 + 5;
const ui INF = 0x3f3f3f3f << 1;
int n, m;
struct node {
    int u, v;
    ui w;
    bool operator < (const node& o)const { return w < o.w; }
}e[N];
vector<piu>G[N];
inline ui min(ui x, ui y) { return x > y ? y : x; }
inline void chmn(ui& x, ui y) { x = x > y ? y : x; }
struct DSU {
    int fa[N];
    ui val[N];
    DSU(int n) { for (int i = 0;i <= n;++i) fa[i] = i, val[i] = INF; }
    inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    inline void merge(int x, int y, ui w = INF) {
        x = find(x), y = find(y);
        fa[x] = y, chmn(val[y], min(val[x], w));
    }
};
inline void kruskal() {
    DSU d(n);
    sort(e + 1, e + m + 1);
    for (int i = 1;i <= m;++i) {
        int u = d.find(e[i].u), v = d.find(e[i].v);
        if (u == v) continue;
        G[e[i].u].push_back(mkp(e[i].v, i));
        G[e[i].v].push_back(mkp(e[i].u, i));
        d.merge(u, v);
    }
}
int cntn, dfn[N], out[N], dep[N], up[N], eid[N];
inline void dfs(int u) {
    dfn[u] = ++cntn;
    for (auto [v, id] : G[u])
        if (v != up[u]) {
            up[v] = u;
            eid[v] = id;
            dep[v] = dep[u] + 1;
            dfs(v);
        }
    out[u] = cntn;
}
ui ans[N];
struct SegmentTree {
#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid (l + r >> 1)
    ui mn[N << 2];
    bool cov[N << 2];
    inline void build(int id, int l, int r) {
        mn[id] = INF, cov[id] = false;
        if (l == r) return;
        build(ls, l, mid), build(rs, mid + 1, r);
    }
    inline void pushdown(int id) {
        if (cov[id]) {
            cov[ls] = cov[rs] = mn[ls] = mn[rs] = INF;
            cov[id] = false;
        }
        if (mn[id] != INF) {
            chmn(mn[ls], mn[id]);
            chmn(mn[rs], mn[id]);
            mn[id] = INF;
        }
    }
    inline void chkmn(int id, int l, int r, int L, int R, ui k) {
        if (l >= L && R >= r) return chmn(mn[id], k);
        pushdown(id);
        if (L <= mid) chkmn(ls, l, mid, L, R, k);
        if (R > mid) chkmn(rs, mid + 1, r, L, R, k);
    }
    inline void cover(int id, int l, int r, int L, int R) {
        if (l >= L && R >= r) return cov[id] = true, mn[id] = INF, void(0);
        pushdown(id);
        if (L <= mid) cover(ls, l, mid, L, R);
        if (R > mid) cover(rs, mid + 1, r, L, R);
    }
    inline ui get(int id, int l, int r, int x) {
        if (l == r) return mn[id];
        pushdown(id);
        return x <= mid ? get(ls, l, mid, x) : get(rs, mid + 1, r, x);
    }
}SGT, SGT2;
int q[N], l, r;
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1;i <= m;++i) scanf("%d%d%u", &e[i].u, &e[i].v, &e[i].w);
    kruskal();
    dfs(1);
    DSU tree(n), ECC(n);
    SGT.build(1, 1, n);
    SGT2.build(1, 1, n);
    for (int i = 1;i <= n;++i) G[i].clear(), ans[i] = INF;
    for (int i = 1;i <= m;++i) {
        if (up[e[i].u] == e[i].v) swap(e[i].u, e[i].v);
        int u = ECC.find(e[i].u), v = ECC.find(e[i].v);
        if (u == v) continue;
        if (eid[e[i].v] == i) {
            SGT2.chkmn(1, 1, n, dfn[v], out[v], e[i].w);
            if (tree.find(u) == 1) {
                SGT.cover(1, 1, n, dfn[v], out[v]);
                l = r = 1;
                q[r++] = v;
                while (l < r) {
                    int u = q[l++];
                    chmn(ans[dfn[u]], SGT2.get(1, 1, n, dfn[u]) + e[i].w);
                    for (auto [v, w] : G[u])
                        if (v != up[u]) q[r++] = v;
                }
            }
            G[e[i].u].push_back(mkp(e[i].v, e[i].w));
            G[e[i].v].push_back(mkp(e[i].u, e[i].w));
            tree.merge(v, u);
            continue;
        }
        while (u != v)
            if (dep[u] < dep[v]) ECC.merge(v, up[v], e[eid[v]].w), v = ECC.find(v);
            else ECC.merge(u, up[u], e[eid[u]].w), u = ECC.find(u);
        SGT.chkmn(1, 1, n, dfn[u], out[u], e[i].w + ECC.val[u]);
        SGT2.chkmn(1, 1, n, dfn[u], out[u], ECC.val[u]);
    }
    for (int i = 2;i <= n;++i) chmn(ans[i], SGT.get(1, 1, n, i));
    for (int i = 2;i <= n;++i) printf("%u\n", ans[dfn[i]]);
    return 0;
}

综合性太强了吧,这不评黑?

Updated on 2026/9/4 14:07:升黑了,舒服了

posted @ 2026-09-04 12:27  keysky  阅读(7)  评论(0)    收藏  举报