洛谷P3979 遥远的国度
标签
- 树链剖分
- LCA
- 换根
简明题意
- 给一棵有根树,支持3种操作:
- 修改:把树的根改为id
- 修改:把u-v路径上的权值改为v
- 查询:查询当前根下,x的子树中的最小权值
思路
- 这里比较麻烦的是换根操作。具体怎么处理呢?当然是不可能每次换根都重新dfs一次。看下图,假设1是最开始的跟,我们从1节点dfs过两次。现在假设root换成了4(1号节点仍然是原根)
- 换根后成了这样:
- 我们可以发现,有很多节点的子树并没有改变,下图用紫色矩形圈出了没有发生变化的节点,红色矩形圈出的是子树发生了变化的节点:
- 也就是说,只有从新根到原根的那一条链(红色矩形圈出)、以及新的根(4号节点)的子树发生了变化,其他节点的子树都没有发生改变。为什么会这样呢?我们可以从父节点的角度考虑,大家模拟一遍以4为新根把树“提起来”的过程,可以发现,只有新根到原根连成的那一条链上的节点的父节点改变了(包括新根和原根)。也就是说除了这条链上的节点外,其他的节点仍按照原先的做法去查询就可以了。现在问题来了,如何对这条链上的节点进行查询操作?
- 设查询的点为x,整棵树-x到新根的路径中x的直系儿子,就是答案了。这里要查的节点是x,x的直系儿子即为u,那么u的范围就是 id[u]---------id[u] + siz[u] - 1,所以呢,我们要查的范围就是1----id[u]-1和id[u]+siz[u]------n。这里会出现一个很严重的问题,就是当我们找到的u的子树中包含了n号节点,那么我们查找的就不是两个范围了,而是一个范围,1----id[u],因为回忆我们查的范围id[u]+siz[u]-----n,其中id[u]+siz[n] = n + 1,仍然查两个范围会RE(这里感谢洛谷用户@chdy帮我查出的bug~~~如果不是她,我估计会多挣扎好多个小时)
- 好了,最后一个问题就是,怎么找到x的直系儿子。这里直系儿子指:x的 在x到新跟路径上的儿子。我在这里直接遍历x的所有儿子v,如果LCA(v, 新根) == v,就说明v是x的直系儿子。
注意事项
- 就是一棵子树,如果子树中包含了n号节点,那么这棵子树的右边是没有区间的!不能查它右边!否则RE!!!
总结
- 换根的题,找对关系分类讨论就好了
AC代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100000 + 10;
int n, m, a[maxn];
int root, cur_root;
vector<int> g[maxn];
int dep[maxn], fa[maxn], siz[maxn], son[maxn];
void dfs1(int u, int f, int deep)
{
dep[u] = deep;
fa[u] = f;
siz[u] = 1;
int max_son = -1;
for (auto& v : g[u])
if (v != f)
{
dfs1(v, u, deep + 1);
siz[u] += siz[v];
if (siz[v] > max_son)
max_son = siz[v], son[u] = v;
}
}
int top[maxn], id[maxn], cnt, w[maxn];
void dfs2(int u, int topf)
{
id[u] = ++cnt;
w[cnt] = a[u];
top[u] = topf;
if (son[u])
{
dfs2(son