「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:升黑了,舒服了

浙公网安备 33010602011771号