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;
}
如有不足,还请指出,谢谢。

浙公网安备 33010602011771号