【全程NOIP计划】树上问题
【全程NOIP计划】树上问题
最近公共祖先
问题
给定一棵树,每次给两个点,求他们的祖先,且该祖先为深度最小
思路
一般来说,我们想到一个暴力做法
查询x,y的话,直接把x的祖先全部标记一遍,然后把y向上遍历,直到遍历到一个点,使得这个点被标记过,这个点就是x和y的最近公共祖先
或者,使得深度更大的第一个点一直向上跳,让x和y的深度一样,让他们一起一步一步向上跳,直到他们遇到同一个点,这样的方法正确性没有问题,它的复杂度和这棵树的深度是有关的
考虑倍增,对于刚才的做法进行优化
原来的方法:\(fa[i]\)表示i的父亲
倍增的方法:\(fa[i][j]\)表示i向上跳\(2^j\)到哪个点
怎么预处理呢?
f[i][0];
for(int j=1;j<=logn;j++)
{
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
这样我们就维护出来了
所以倍增lca的步骤就是:
1.先让y的深度比x大,然后将y和x调到深度相同的位置
2.然后把log从小到大枚举,如果跳完了到的是同一个点,他们就不跳,如果不是同一个点,那么他们就向上跳,这样重复的话一定可以跳到同一个点
3.我们可以唯一确定该这个拆分(不考虑顺序
基础问题
题目
给一个n个点的树,有m次查询,每次查x到y路上的点权和
思路
可以利用树上前缀和和最近公共祖先
发现答案就是x的深度加上y的深度,减去lca的深度,再减去lca父亲的深度就可以了
又一个基础问题
题目
给一个n个节点的树,有m次查询,每次给定两个点x,y,求x是不是y的祖先
思路
直接判断x和y的最近公共祖先是不是他们其中一个不就得了
不不不,下面有一个神奇的玩意儿
DFS序
实际上就是维护一下一个点是什么时候dfs到的,或者什么时候走入它的子树,走出它的子树的
int cnt;
void dfs(int x)
{
l[x]=++cnt;
for(x的每个儿子)
dfs(对应的儿子);
r[x]=cnt;
}
对于x子树内的每个y,都有\(l[x]\le l[y]\le r[x]\),
上面的那道题目直接看y的区间是不是在进入x的和走出x的区间之内就好了
P3128
思路
x到根的路径加一,然后\(b[x]++\)
P2680 运输计划
思路
最长的最小,让我们想到了二分答案
发现,两条路径只能变短,不能变长
我们可以用一个树上差分做法来做
经典问题
题目
给一个n个节点的数,有点权,有m次查询,每次给三个数x,y,z,求x,y之间有多少点值=z,假设值域\(O(n)\)
思路
树上差分的做法
直接查一个点到根上面的z有多少个,dfs到一个点的时候,我们直接维护一个值域上的数组
未公开题目
题目
给一棵n个节点的树,有m次询问
每次查询:从x点开始走向y点,每秒走一步,假设在第i秒走到了i点,则答案加一,求答案
思路
P1600 天天爱跑步
思路
每个选手可以被差分掉
把x到y的路径的选手先分成x到lca,以及lca到y
LOJ6276
思路
P1967 货车运输
思路
P1352 没有上司的舞会
思路
就一个树形DP,骚简单
未公开题目
题目
一个图,保证每个点最多在一个简单环里面,问你这个图上面有多少个眼镜