P9433 Stage5 - Conveyors

传送门

个人认为,做这种题把思路想清晰代码自然就写出来了,所以要注重思考的过程,重点把握一些自己想不到的性质或技巧。

本篇题解在现有题解的基础上对解法进行了更详细的说明。阅读前建议确保自己会树链剖分和树剖求最近公共祖先。

解题报告

约定:\(dis_{a,b}\) 表示 \(a\)\(b\) 的简单路径,\(\text{LCA}(a,b)\) 表示 \(a,b\) 的最近公共祖先。

由于需要把所有关键节点都走一遍,所以我们不可避免地需要把包含所有关键节点的最小连通块求出来,同时计算该连通块内的边权和 \(sum\)

接下来我们观察询问的 \(s,t\) 节点,猜想答案与 \(s,t\) 的位置相关,于是我们进行分类讨论。

  • \(s,t\) 均在包含所有关键节点的最小连通块上时,答案即为 \(2\times sum-dis_{s,t}\)。因为此时的最短路径为 \(s\)\(t\) 的简单路径走一遍,然后连通块其他边走两遍。该情况对应样例中的第 \(1,2\) 次询问。
  • \(s\)\(t\) 中有一个不在该最小连通块上时,假设 \(s\) 不在,求出 \(s\) 最靠近的连通块上的点 \(x\),则由第一种情况容易得出答案为 \(dis_{s,x}+2\times sum-dis_{x,t}\)
  • \(s\)\(t\) 都不在该最小连通块上时,求出 \(s\) 最靠近的连通块上的点 \(x\)\(t\) 最靠近的连通块上的点 \(y\),由第二种情况容易推出答案为 \(dis_{s,x}+2\times sum-dis_{x,y}+dis_{y,t}\)

综上所述,求答案的式子就是 \(dis_{s,x}+2\times sum-dis_{x,y}+dis_{y,t}\)。接下来考虑如何实现。

主要任务之一是快速求 \(dis_{a,b}\)。我们令 \(dis(x)\) 表示第一个关键节点到 \(x\) 的最短距离,则 \(dis_{a,b}=dis(a)+dis(b)-2\times dis(\text{LCA}(a,b))\)

另外一个主要任务就是求连通块以外的点最靠近的连通块上的点,这使用一次 DFS,从连通块上的点向外拓展即可。

参考代码

#include <iostream> 
#define reg register
#define IL inline
using namespace std;
const int N = 1e5 + 10; 
int n, q, k, key[N]; 
bool IMP[N];  
int head[N], cnt;   
struct EDGE{int to, val, nxt; } edge[N << 1]; 
IL void addedge(reg int u, reg int v, reg int w) {
	edge[++cnt].to = v, edge[cnt].val = w, edge[cnt].nxt = head[u]; 
	head[u] = cnt; return;  
}//链式前向星存树 
int fa[N], dep[N], sz[N], son[N], top[N]; 
//基本信息:祖先fa,深度dep,子树大小sz,重儿子son,链头top 
int dis[N], tonear[N], ct[N], near[N], sum;  
IL void dfs1(reg int u, reg int p) {
	fa[u] = p, sz[u] = 1, dep[u] = dep[p] + 1; 
	for(reg int i = head[u], v, w; i; i = edge[i].nxt) {
		v = edge[i].to, w = edge[i].val; 
		if(v == p) continue; 
		dis[v] = dis[u] + w; //求dis(x) 
		dfs1(v, u); 
		ct[u] += ct[v]; 
		if(ct[v] && k - ct[v] > 0) IMP[u] = 1; //判断是否在最小连通块上 
		sz[u] += sz[v]; 
		if(!son[u] || sz[son[u]] < sz[v]) son[u] = v;  
	}
	return; 
}
IL void dfs2(reg int u, reg int topu) {
	top[u] = topu; 
	if(!son[u]) return; 
	dfs2(son[u], topu); 
	for(reg int i = head[u], v; i; i = edge[i].nxt) {
		v = edge[i].to; 
		if(v != fa[u] && v != son[u]) dfs2(v, v);  
	} 
	return; 
} //树剖基本操作 
IL int lca(reg int x, reg int y) {
	while(top[x] != top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x, y); 
		x = fa[top[x]]; //跳链 
	} 
	if(dep[x] > dep[y]) swap(x, y); 
	return x; 
} //树剖lca 
IL void dfs(reg int u, reg int p, reg int now, reg int dist) {
	near[u] = now, tonear[u] = dist; //求最近点near以及u到最近点的距离 
	for(reg int i = head[u], v, w; i; i = edge[i].nxt) {
		v = edge[i].to, w = edge[i].val; 
		if(v == p) continue; 
		if(IMP[v]) sum += w, dfs(v, u, v, dist); 
		else dfs(v, u, now, dist + w);  
	}
	return; 
} 
IL int calc(reg int u, reg int v) {
	reg int tmp = dis[near[u]] + dis[near[v]] - (dis[lca(near[u], near[v])] << 1); 
	return tonear[u] + (sum << 1) - tmp + tonear[v]; //计算 
}
int main(){
	scanf("%d %d %d", &n, &q, &k); 
	for(reg int i = 1, u, v, w; i < n; i++) {
		scanf("%d %d %d", &u, &v, &w); addedge(u, v, w), addedge(v, u, w); 
	} 
	for(reg int i = 1; i <= k; i++) {
		scanf("%d", &key[i]); IMP[key[i]] = true; 
		ct[key[i]] = 1; 
	} 
	dfs1(1, 1), dfs2(1, 1); dfs(key[1], 0, key[1], 0); 
	while(q--) {
		reg int u, v; scanf("%d %d", &u, &v); 
		printf("%d\n", calc(u, v)); 
	}
	return 0;
}

如有不足,还请指出,谢谢。

posted @ 2025-07-19 23:35  Tiger_Rory  阅读(234)  评论(0)    收藏  举报
//雪花飘落效果