题解: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;
}
posted @ 2026-09-03 17:28  xuyifei0302  阅读(9)  评论(0)    收藏  举报