题解:P11095 [ROI 2021] 旅行 (Day 2)
读完题,我们发现题目要求在无向图中寻找一条从首都(城市 \(1\))到各个城市 \(k\) 的路径,可以多次经过同一个城市,但不能重复经过同一条边。我们要最小化这条路径上的最大边权与最小边权之和。
由于不能重复经过同一条边,如果我们从主干路径上岔开去走一个环,我们必须能顺着这个环走一圈并回到主干路径上。
也就是说,我们最终的旅行路线可以抽象为:一条从 \(1\) 到 \(k\) 的简单路径,以及挂在这条路径上的一系列双连通分量。
我们不妨先考虑一个简化情况:图中没有环,是一棵树。
显然对于这种情况我们只能沿着树上唯一的简单路径走,此时答案就是路径上的最大边加上最小边。为了让最大边尽可能小,我们自然而然会想到最小生成树。
我们将图的边按权值升序排序,求出最小生成森林。在最小生成森林上从 \(1\) 到 \(k\) 的路径,其最大边权 \(MaxW_k\) 已经是所有可能连通路径中最小的了。
那么非树边有什么用呢?
非树边会与树边形成环。
我们假设有一条权值为 \(W\) 的非树边 \((u, v)\),它与最小生成森林上的路径共同构成了一个环。
由于我们是按边权升序加边的,所以 \(W\) 一定是这个环里最大的边。设这个环里最小的树边为 \(V\)。
- 如果 \(1 \to k\) 的主干路径经过了这个环上的任意一点,旅行者就可以顺着这个环走一圈。
- 走完这个环后,整条路线的最大边权会更新为 \(\max(MaxW_k, W)\),最小边权会更新为 \(\min(MinW_k, V)\)。
于是,题目就变为:我们需要找出图中所有由非树边形成的环所对应的 \((W, V)\),并在查询城市 \(k\) 时,从所有它能“顺路”经过的环中,挑选一个最优的环,使得 \(\max(MaxW_k, W) + \min(MinW_k, V)\) 最小。
但是我们又产生了新的问题,随着边的加入,环会不断嵌套、合并,如何动态维护这些环并提取最小边 \(V\)。
这里我们使用 LCT 结合并查集动态缩点。
首先我们按边权从小到大处理所有边,并用 LCT 维护当前的森林。当遇到一条边 \((u, v)\) 时,如果 \(u\) 和 \(v\) 在不同的连通块,直接在 LCT 中 link(u, v),作为树边。如果 \(u\) 和 \(v\) 已经在同一个连通块,说明形成了一个环。我们在 LCT 中 split(u, v) 提取出这条路径。此时 LCT 维护的信息就能直接告诉我们这个环上的最小树边 \(V\)。
接下来是缩点操作。
既然这个环已经被形成,且后续任何经过该环的路径都可以利用它,我们在逻辑上可以将这个环里的所有点缩成一个大点。具体实现上,我们新建一个代表该连通分量的虚点,记录它能提供的环信息 \((W, V)\),并利用并查集把原路径上的点都指向这个虚点,同时在 LCT 中断开原有的边,将它们连到虚点上。
通过 LCT 动态缩点,我们不仅找出了所有有效的 \((W, V)\),还顺便构建出了一棵连通分量树。在原树中,这个环所在子树的最顶端,就是进入这个连通分量的“入口”。
经过上述缩点后,我们得到了:
- 一棵缩点后的最小生成树,以 \(1\) 为根。
- 每个点 \(k\) 到根的树上路径有着固定的最大边 \(MaxW_k\) 和最小边 \(MinW_k\)。
- 每个连通分量入口处挂载了若干个 \((W, V)\) 的环信息。一个环信息对以该入口为根的整个子树都是可见且可用的。
这变成了一个树上离线查询问题:
我们在树上进行一次 DFS,进入节点时,把该节点挂载的所有 \((W, V)\) 信息加入数据结构;离开节点时撤销。对于当前到达的节点 \(k\),我们需要在数据结构中查询 \(\min \{ \max(MaxW_k, W) + \min(MinW_k, V) \}\)。
我们可以用线段树实现。
我们将线段树按环的最大边权 \(W\) 进行离散化建树。查询可以分为两部分:
- 当 \(W \le MaxW_k\) 时:\(\max(MaxW_k, W) = MaxW_k\)。我们需要最小化 \(MaxW_k + \min(MinW_k, V)\)。因此只需在线段树的 \([1, MaxW_k]\) 区间内查询最小的 \(V\),取出后更新答案即可。
- 当 \(W > MaxW_k\) 时:\(\max(MaxW_k, W) = W\)。式子变为 \(W + \min(MinW_k, V)\)。这等价于 \(\min(W + V, W + MinW_k)\)。我们只需在线段树的 \((MaxW_k, +\infty)\) 区间内,分别查询最小的 \((W+V)\) 和最小的 \(W\)。
线段树的每个叶子节点维护一个 multiset 来支持动态插入和删除 \(V\),内部节点维护区间最小的 \(V\)、最小的 \(W\) 以及最小的 \(W+V\)。
时间复杂度 \(O(M \log M + M \log N + (N + M) \log M)\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e18;
namespace LCT {
struct Node {
int lson, rson, father, val, sum, tag;
} tree[1000005];
int f[1000005];
int get_father(int x) {
return x == f[x] ? x : f[x] = get_father(f[x]);
}
int tree_fa(int p) {
return tree[p].father = get_father(tree[p].father);
}
void pushup(int p) {
tree[p].sum = tree[p].val;
if (tree[p].lson) {
tree[p].sum = min(tree[p].sum, tree[tree[p].lson].sum);
}
if (tree[p].rson) {
tree[p].sum = min(tree[p].sum, tree[tree[p].rson].sum);
}
}
void pushdown(int p) {
if (tree[p].tag) {
swap(tree[p].lson, tree[p].rson);
if (tree[p].lson) {
tree[tree[p].lson].tag ^= 1;
}
if (tree[p].rson) {
tree[tree[p].rson].tag ^= 1;
}
tree[p].tag = 0;
}
}
int identify(int p) {
int fa = tree_fa(p);
if (fa == 0) {
return -1;
}
if (tree[fa].lson == p) {
return 0;
}
if (tree[fa].rson == p) {
return 1;
}
return -1;
}
void connect(int p, int father, int op) {
if (p) {
tree[p].father = father;
}
if (op == 0) {
tree[father].lson = p;
}
if (op == 1) {
tree[father].rson = p;
}
}
void rotate(int p) {
int x = tree_fa(p), y = tree_fa(x), opp = identify(p), opx = identify(x), u = (opp == 1) ? tree[p].lson : tree[p].rson;
connect(u, x, opp);
connect(x, p, opp ^ 1);
if (p) {
tree[p].father = y;
}
if (opx != -1) {
if (opx == 0) {
tree[y].lson = p;
} else {
tree[y].rson = p;
}
}
pushup(x);
pushup(p);
}
void pushall(int p) {
if (identify(p) != -1) {
pushall(tree_fa(p));
}
pushdown(p);
}
void splay(int p) {
pushall(p);
while (identify(p) != -1) {
int x = tree_fa(p);
if (identify(x) == -1) {
rotate(p);
} else if (identify(p) == identify(x)) {
rotate(x);
rotate(p);
} else {
rotate(p);
rotate(p);
}
}
}
void access(int p) {
int u = 0;
while (p) {
splay(p);
tree[p].rson = u;
pushup(p);
u = p;
p = tree_fa(p);
}
}
void makeroot(int p) {
access(p);
splay(p);
swap(tree[p].lson, tree[p].rson);
if (tree[p].lson) {
tree[tree[p].lson].tag ^= 1;
}
if (tree[p].rson) {
tree[tree[p].rson].tag ^= 1;
}
}
int findroot(int p) {
access(p);
splay(p);
while (tree[p].lson) {
pushdown(p);
p = tree[p].lson;
}
splay(p);
return p;
}
void split(int x, int y) {
makeroot(x);
access(y);
splay(y);
}
void link(int x, int y) {
makeroot(x);
tree[x].father = y;
}
}
struct Node {
int u, v, w;
bool operator<(const Node& o) const {
return w < o.w;
}
} edge[300005];
struct N {
int x, y;
};
struct N1 {
int v, w;
};
int n, m, pointid, fa[1000005], a[1000005], b[1000005], edgeu[1000005], edgev[1000005], deep[300005], belong[1000005], minnval[1200005], minnw[1200005], sum[1200005], ans[300005];
bool mark[1000005];
vector<int> val;
vector<N> upd[300005];
vector<N1> gra[300005];
multiset<int> tree[300005];
void dfs(int p, int x) {
if (!p) {
return;
}
LCT::pushdown(p);
fa[p] = x;
LCT::f[p] = x;
dfs(LCT::tree[p].lson, x);
dfs(LCT::tree[p].rson, x);
}
void dfs1(int u, int father, int d) {
deep[u] = d;
for (auto i : gra[u]) {
if (i.v != father) {
dfs1(i.v, u, d + 1);
}
}
}
void buildtree(int p, int l, int r) {
minnval[p] = INF;
minnw[p] = INF;
sum[p] = INF;
if (l == r) {
return;
}
int mid = (l + r) >> 1;
buildtree(p * 2, l, mid);
buildtree(p * 2 + 1, mid + 1, r);
}
void change(int p, int l, int r, int x, int y, int z, int op) {
if (l == r) {
if (op == 1) {
tree[l].insert(z);
} else {
tree[l].erase(tree[l].find(z));
}
if (tree[l].empty()) {
minnval[p] = INF;
minnw[p] = INF;
sum[p] = INF;
} else {
minnval[p] = *tree[l].begin();
minnw[p] = y;
sum[p] = minnw[p] + minnval[p];
}
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
change(p * 2, l, mid, x, y, z, op);
} else {
change(p * 2 + 1, mid + 1, r, x, y, z, op);
}
minnval[p] = min(minnval[p * 2], minnval[p * 2 + 1]);
minnw[p] = min(minnw[p * 2], minnw[p * 2 + 1]);
sum[p] = min(sum[p * 2], sum[p * 2 + 1]);
}
int get_min(int p, int l, int r, int ll, int rr) {
if (ll <= l && r <= rr) {
return minnval[p];
}
int mid = (l + r) >> 1, minn = INF;
if (ll <= mid) {
minn = min(minn, get_min(p * 2, l, mid, ll, rr));
}
if (rr > mid) {
minn = min(minn, get_min(p * 2 + 1, mid + 1, r, ll, rr));
}
return minn;
}
int get_right(int p, int l, int r, int ll, int rr, int x) {
if (minnval[p] == INF) {
return INF;
}
if (ll <= l && r <= rr) {
return min(sum[p], minnw[p] + x);
}
int mid = (l + r) >> 1, minn = INF;
if (ll <= mid) {
minn = min(minn, get_right(p * 2, l, mid, ll, rr, x));
}
if (rr > mid) {
minn = min(minn, get_right(p * 2 + 1, mid + 1, r, ll, rr, x));
}
return minn;
}
int get_posw(int w) {
return lower_bound(val.begin(), val.end(), w) - val.begin() + 1;
}
int get_posle(int w) {
return upper_bound(val.begin(), val.end(), w) - val.begin();
}
void dfs2(int u, int father, int maxn, int minn) {
if (!val.empty()) {
for (auto i : upd[u]) {
change(1, 1, (int)val.size(), get_posw(i.x), i.x, i.y, 1);
}
}
if (u != 1) {
ans[u] = maxn + minn;
if (!val.empty()) {
int maxw_idx = get_posle(maxn);
if (maxw_idx >= 1) {
int vmin = get_min(1, 1, (int)val.size(), 1, maxw_idx);
if (vmin != INF) {
ans[u] = min(ans[u], maxn + min(vmin, minn));
}
}
if (maxw_idx < (int)val.size()) {
int r_cost = get_right(1, 1, (int)val.size(), maxw_idx + 1, (int)val.size(), minn);
if (r_cost != INF) {
ans[u] = min(ans[u], r_cost);
}
}
}
}
for (auto i : gra[u]) {
if (i.v != father) {
dfs2(i.v, u, max(maxn, i.w), min(minn, i.w));
}
}
if (!val.empty()) {
for (auto i : upd[u]) {
change(1, 1, (int)val.size(), get_posw(i.x), i.x, i.y, -1);
}
}
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= 1000000; i ++) {
LCT::f[i] = i;
LCT::tree[i].val = INF;
LCT::tree[i].sum = INF;
}
pointid = n;
for (int i = 1; i <= m; i ++) {
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
sort(edge + 1, edge + m + 1);
for (int i = 1; i <= m; i ++) {
int u = LCT::get_father(edge[i].u), v = LCT::get_father(edge[i].v);
if (u == v) {
continue;
}
if (LCT::findroot(u) != LCT::findroot(v)) {
pointid ++;
LCT::tree[pointid].val = edge[i].w;
LCT::tree[pointid].sum = edge[i].w;
LCT::link(u, pointid);
LCT::link(pointid, v);
edgeu[pointid] = edge[i].u;
edgev[pointid] = edge[i].v;
gra[edge[i].u].push_back({edge[i].v, edge[i].w});
gra[edge[i].v].push_back({edge[i].u, edge[i].w});
} else {
LCT::split(u, v);
int y = LCT::tree[v].sum;
pointid ++;
LCT::tree[pointid].val = y;
LCT::tree[pointid].sum = y;
dfs(v, pointid);
LCT::tree[pointid].lson = LCT::tree[pointid].rson = 0;
LCT::tree[pointid].father = 0;
a[pointid] = edge[i].w;
b[pointid] = y;
mark[pointid] = true;
}
}
dfs1(1, 0, 1);
for (int i = 1; i <= n; i ++) {
belong[i] = i;
}
for (int i = n + 1; i <= pointid; i ++) {
if (!mark[i] && edgeu[i]) {
if (deep[edgeu[i]] < deep[edgev[i]]) {
belong[i] = edgeu[i];
} else {
belong[i] = edgev[i];
}
}
}
for (int i = 1; i <= pointid; i ++) {
int p = fa[i];
if (p && belong[i]) {
if (!belong[p]) {
belong[p] = belong[i];
} else if (deep[belong[i]] < deep[belong[p]]) {
belong[p] = belong[i];
}
}
}
for (int i = n + 1; i <= pointid; i ++) {
if (mark[i] && belong[i]) {
upd[belong[i]].push_back({a[i], b[i]});
val.push_back(a[i]);
}
}
sort(val.begin(), val.end());
val.erase(unique(val.begin(), val.end()), val.end());
if (!val.empty()) {
buildtree(1, 1, (int)val.size());
}
dfs2(1, 0, 0, INF);
for (int i = 2; i <= n; i ++) {
cout << ans[i] << "\n";
}
return 0;
}
本文来自博客园,作者:xuyifei0302,转载请注明原文链接:https://www.cnblogs.com/xuyifei0302/p/22828966

浙公网安备 33010602011771号