树上差分就是说;
其实树上的问题真的和线性的问题是很像的只不过是迭代方式发生了一个变化;
线性的前缀和:前缀和;
线性的差分:差分;
线性的这两个东西的结合:线段树;
在树上就分别是:
开一个数组表示这个节点的子树上的权值之和;
树上差分;
树链剖分;
第一个树上面的前缀和就不讲了,很简单;
来个树上差分;
首先我们要知道树上差分维护的是一条边的值;
倞阶指南上面有两幅图画的好;
每次求边的值的时候会把大半棵树遍历一遍;
在那条边的两个节点上打上+1的标记然后在LCA上打上-2的标记;
代码
int dfs(int u, int father)
{
int res = d[u];
//此处的D就是差分标记的数组;代表的是这个点跟他爸爸相连的边的权值;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != father)
{
int s = dfs(j, u);
res += s;
}
}
return res;
}
树链剖分,就是说,一个区间里面,如果既要完成前缀和也要完成差分的功能的话那么就只能用线段树树状数组分块等方法,而在一个树上如果既要完成差分又要完成前缀和那么就应该用树链剖分和线段树;
线段树里面的每个点的u表示的是原来树上的一条边;
现在是主要内容,我们通过两次dfs求出各种前置的必要的信息,然后将对于每个节点,他的子树里面最大的那个就是重儿子,连接他和重儿子以及重儿子的儿子一直到底的那条边叫做重链;
重链的所有长度必然大于等于轻链的长度,这里运用了贪心选择的性质,不然的话我们就可以使用调整法来进行一个替换,然后第二次dfs的时候我们保证每次都先去遍历重儿子,然后第二次dfs遍历出来的顺序就是线段树里面的真实顺序,然后这样子我们就能保证同一条重链一定都是连在一起的,这样子就可以最小化线段树的修改次数不用 O ( N ) O(N) O(N)的modify了,时间复杂度是 O ( n ∗ l o g n ∗ l o g n ) O(n*logn*logn) O(n∗logn∗logn);
树链剖分的函数主要能分成三个部分,第一个是线段树操作函数,第二个是树链剖分链操作函数,第三个是树链剖分树操作函数;
本质上就是寻找一种方法将树的迭代顺序拆成是一条链,然后就能便于我们去维护各种区间内的值;
例题在acwing上面有;
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100010,M=N*2;
int n,m;
int w[N],h[N],e[M],ne[M],idx;
int id[N],nw[N],cnt;
int dep[N],sz[N],top[N],fa[N],son[N];
struct seg{
int l,r;
ll add,sum;
}tr[N*4]