树链剖分有两个用途:
①求树上节点x到节点y的路径上的点权和
②改变树上节点x到节点y的路径上所有点的点权
但是,这两个用途不都可以用LCA实现吗?
是的,但是我们要求的是在线的修改和查询,LCA这种东西除非你敢修改一次就重新建一次LCA,这时间一下子就飙上去了。
所以,这正是我们学树链剖分的意图。
重儿子:父亲结点的所有儿子结点中,子树拥有结点数最多的结点。
轻儿子:父亲结点除了重儿子以外的所有儿子。
重边:父亲结点与重儿子的连边。
轻边:父亲结点与轻儿子的连边。
重链:所有重边所组成的一条链。
f[u]:保存点u的父亲结点
d[u]:保存点u的深度
size[u]:保存以u为根节点的子树的结点个数
son[u]:保存点u的重儿子
top[u]:保存点u所在重链的顶端结点
id[u]:保存点u进行重新编号后的新的编号
这个其实不太难,但一定要把上面的概念理解清楚。
既然我们定义了这么多变量,定然是要一个一个处理出来的撒。
首先,大家看,第一遍DFS能够处理出来那些数据:
定然是:f[u], d[u], size[u], son[u]
Code:
void dfs1 (int x, int fa, int dep){//x是指此节点,fa是指父节点,dep是指当前深度
d[x] = dep;
siz[x] = 1;
f[x] = fa;
int maxs = -1;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != fa){
dfs1 (tmp, x, dep + 1);
if (siz[tmp] > maxs)//建议大家像这样写,其他写法可能有bug
son[x] = tmp, maxs = siz[tmp];
siz[x] += siz[tmp];
}
}
}
然后,看懂了第一个深搜,就来处理top[u], id[u]。
注意,我们处理出来之后,同一条重链上的点的id是从上到下递增的。
这就有点考技术,大家想一想,详见代码。
Code:
void dfs2 (int x, int fa){//我这里id是用dfn代替的,这个fa其实并不代表父亲节点,而是代表重链的顶点
cnt ++;
top[x] = fa;
dfn[x] = cnt;
w[cnt] = val[x];
if (! son[x])//如果重链到头了,就返回
return ;
dfs2 (son[x], fa);
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != f[x] && tmp != son[x]){//枚举下一条重链的开端
dfs2 (tmp, tmp);
}
}
}
重头戏来了,现在,我们正式开始操作。
这里我们就要结合线段树了。
我们建一棵线段树出来(注意,按每个点的新编号进行建树)
然后,选节点x和节点y中深度较深的一个点出来进行爬树操作(爬到它那条重链的顶点的父亲节点)
然后一边爬一边通过线段树求和就行了。
爬到什么时候为止呢?
自然是两个节点已经到了同一条重链的时候结束。
最后再加上爬完树之后两点之间的点权和就行了。
其实挺好理解的。
Code
int get_sum (int x, int y){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])//让节点x成为深度最深的节点
swap (x, y);
Sum = (Sum + Query (1, dfn[top[x]], dfn[x])) % p;//就和
x = f[top[x]];//爬到重链顶尖的父节点
}
if (dfn[x] > dfn[y])//最后再加上爬完树之后两点之间的点权和
Sum = (Sum + Query (1, dfn[y], dfn[x])) % p;
else
Sum = (Sum + Query (1, dfn[x], dfn[y])) % p;
return Sum;
}
显而易见,就是把求和改成了修改,将上面的代码改一下就行了。
Code:
int Add (int x, int y, int z){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap (x, y);
Update (1, dfn[top[x]], dfn[x], z);
x = f[top[x]];
}
if (d[x] > d[y])
swap (x, y);
Update (1, dfn[x], dfn[y], z);
}
如果线段树给打错了,肯定是十分恼火的。因为有时候这本是一道树链剖分的题,被你给做成了一道线段树的黑题。
错误一般都集中在建树和下传懒标记上,下面给出代码:
//建树
void build (int l, int r, int Index){
a[Index].L = l;
a[Index].R = r;
a[Index].lazy = a[Index].s = 0;
if (l != r){
int mid = (l + r) / 2;
build (l, mid, Index * 2);
build (mid + 1, r, Index * 2 + 1);
a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
}
else
a[Index].s = w[l] % p;//注意
}
//下传懒标记
void push_down (int Index){
if (a[Index].L != a[Index].R){
a[Index * 2].lazy = (a[Index * 2].lazy + a[Index].lazy) % p;
a[Index * 2 + 1].lazy = (a[Index * 2 + 1].lazy + a[Index].lazy) % p;
a[Index * 2].s = (a[Index * 2].s + (a[Index * 2].R - a[Index * 2].L + 1) * a[Index].lazy % p) % p;
a[Index * 2 + 1].s = (a[Index * 2 + 1].s + (a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1) * a[Index].lazy % p) % p;
}
a[Index].lazy = 0;
}
传送门
会板子就会做
#include
#include
#include
#include
#include
#include
using namespace std;
#define M 100005
struct node {
int L, R, lazy, s;
}a[M * 4];
vector <int> G[M];
int n, m, r, p, val[M], siz[M], son[M], cnt, dfn[M], w[M], top[M], d[M], f[M];
void build (int l, int r, int Index){
a[Index].L = l;
a[Index].R = r;
a[Index].lazy = a[Index].s = 0;
if (l != r){
int mid = (l + r) / 2;
build (l, mid, Index * 2);
build (mid + 1, r, Index * 2 + 1);
a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
}
else
a[Index].s = w[l] % p;
}
void push_down (int Index){
if (a[Index].L != a[Index].R){
a[Index * 2].lazy = (a[Index * 2].lazy + a[Index].lazy) % p;
a[Index * 2 + 1].lazy = (a[Index * 2 + 1].lazy + a[Index].lazy) % p;
a[Index * 2].s = (a[Index * 2].s + (a[Index * 2].R - a[Index * 2].L + 1) * a[Index].lazy % p) % p;
a[Index * 2 + 1].s = (a[Index * 2 + 1].s + (a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1) * a[Index].lazy % p) % p;
}
a[Index].lazy = 0;
}
void Update (int Index, int l, int r, int v){
if (a[Index].L > r || a[Index].R < l)
return ;
push_down (Index);
if (l <= a[Index].L && a[Index].R <= r){
a[Index].lazy = v;
a[Index].s = (a[Index].s + (a[Index].R - a[Index].L + 1) * v % p) % p;
return ;
}
Update (Index * 2, l, r, v);
Update (Index * 2 + 1, l, r, v);
a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s) % p;
}
int Query (int Index, int l, int r){
if (a[Index].L > r || a[Index].R < l)
return 0;
push_down (Index);
if (l <= a[Index].L && a[Index].R <= r)
return a[Index].s;
return (Query (Index * 2, l, r) + Query (Index * 2 + 1, l, r)) % p;
}
void dfs1 (int x, int fa, int dep){
d[x] = dep;
siz[x] = 1;
f[x] = fa;
int maxs = -1;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != fa){
dfs1 (tmp, x, dep + 1);
if (siz[tmp] > maxs)
son[x] = tmp, maxs = siz[tmp];
siz[x] += siz[tmp];
}
}
}
void dfs2 (int x, int fa){
cnt ++;
top[x] = fa;
dfn[x] = cnt;
w[cnt] = val[x];
if (! son[x])
return ;
dfs2 (son[x], fa);
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != f[x] && tmp != son[x]){
dfs2 (tmp, tmp);
}
}
}
int get_sum (int x, int y){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap (x, y);
Sum = (Sum + Query (1, dfn[top[x]], dfn[x])) % p;
x = f[top[x]];
}
if (dfn[x] > dfn[y])
Sum = (Sum + Query (1, dfn[y], dfn[x])) % p;
else
Sum = (Sum + Query (1, dfn[x], dfn[y])) % p;
return Sum;
}
int Add (int x, int y, int z){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap (x, y);
Update (1, dfn[top[x]], dfn[x], z);
x = f[top[x]];
}
if (d[x] > d[y])
swap (x, y);
Update (1, dfn[x], dfn[y], z);
}
int main (){
scanf ("%d %d %d %d", &n, &m, &r, &p);
for (int i = 1; i <= n; i ++)
scanf ("%d", &val[i]);
for (int i = 1; i < n; i ++){
int u, v;
scanf ("%d %d", &u, &v);
G[u].push_back (v);
G[v].push_back (u);
}
dfs1 (r, 0, 1);
dfs2 (r, r);
build (1, n, 1);
while (m --){
int f, x, y, z;
scanf ("%d", &f);
if (f == 1){
scanf ("%d %d %d", &x, &y, &z);
z %= p;
Add (x, y, z);
}
if (f == 2){
scanf ("%d %d", &x, &y);
printf ("%d\n", get_sum (x, y));
}
if (f == 3){
scanf ("%d %d", &x, &z);
z %= p;
Update (1, dfn[x], dfn[x] + siz[x] - 1, z);
}
if (f == 4){
scanf ("%d", &x);
printf ("%d\n", Query (1, dfn[x], dfn[x] + siz[x] - 1));
}
}
return 0;
}
这道题终究是个板题。你就把0和1看成每个点的点权,修改和求和都不变。
但是有个优化,就是求和的时候不必用板子的方法,直接用线段树进行修改,把修改后的根节点的值和原根节点的值做差就是改变的点权和。
#include
#include
#include
#include
#include
#include
using namespace std;
#define M 100005
struct node {
int L, R, lazy, s;
}a[M * 4];
vector <int> G[M];
int n, val[M], siz[M], son[M], cnt, dfn[M], w[M], top[M], d[M], f[M], q;
void build (int l, int r, int Index){
a[Index].L = l;
a[Index].R = r;
a[Index].lazy = 0;
a[Index].s = r - l + 1;
if (l != r){
int mid = (l + r) / 2;
build (l, mid, Index * 2);
build (mid + 1, r, Index * 2 + 1);
}
}
void push_down (int Index){
if (a[Index].L != a[Index].R){
if (a[Index].lazy == -1){
a[Index * 2].lazy = a[Index * 2 + 1].lazy = -1;
a[Index * 2].s = a[Index * 2 + 1].s = 0;
}
if (a[Index].lazy == 1){
a[Index * 2].lazy = a[Index * 2 + 1].lazy = 1;
a[Index * 2].s = a[Index * 2].R - a[Index * 2].L + 1;
a[Index * 2 + 1].s = a[Index * 2 + 1].R - a[Index * 2 + 1].L + 1;
}
}
a[Index].lazy = 0;
}
void Update (int Index, int l, int r, int v){
if (a[Index].L > r || a[Index].R < l)
return ;
push_down (Index);
if (l <= a[Index].L && a[Index].R <= r){
a[Index].lazy = v;
if (v == -1)
a[Index].s = 0;
else
a[Index].s = a[Index].R - a[Index].L + 1;
return ;
}
Update (Index * 2, l, r, v);
Update (Index * 2 + 1, l, r, v);
a[Index].s = a[Index * 2].s + a[Index * 2 + 1].s;
}
int Query (int Index, int l, int r){
if (a[Index].L > r || a[Index].R < l)
return 0;
push_down (Index);
if (l <= a[Index].L && a[Index].R <= r)
return a[Index].s;
return Query (Index * 2, l, r) + Query (Index * 2 + 1, l, r);
}
void dfs1 (int x, int fa, int dep){
d[x] = dep;
siz[x] = 1;
f[x] = fa;
int maxs = -1;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != fa){
dfs1 (tmp, x, dep + 1);
if (siz[tmp] > maxs)
son[x] = tmp, maxs = siz[tmp];
siz[x] += siz[tmp];
}
}
}
void dfs2 (int x, int fa){
cnt ++;
top[x] = fa;
dfn[x] = cnt;
w[cnt] = val[x];
if (! son[x])
return ;
dfs2 (son[x], fa);
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != f[x] && tmp != son[x]){
dfs2 (tmp, tmp);
}
}
}
void Add (int x, int y){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap (x, y);
Update (1, dfn[top[x]], dfn[x], -1);
x = f[top[x]];
}
if (dfn[x] > dfn[y]){
Update (1, dfn[y], dfn[x], -1);
}
else{
Update (1, dfn[x], dfn[y], -1);
}
}
int main (){
scanf ("%d", &n);
val[1] = 1;
for (int i = 1; i < n; i ++){
int u;
scanf ("%d", &u);
G[i + 1].push_back (u + 1);
G[u + 1].push_back (i + 1);
val[i + 1] = 1;
}
scanf ("%d", &q);
dfs1 (1, 0, 1);
dfs2 (1, 1);
build (1, n + 1, 1);
while (q --){
char c[20];
int u;
scanf ("%s%d", c, &u);
u ++;
if (c[0] == 'i'){
int t = a[1].s;
Add (1, u);
printf ("%d\n", t - a[1].s);//直接将修改前后的根节点做差就是答案了
}
else{
printf ("%d\n", siz[u] - Query (1, dfn[u], dfn[u] + siz[u] - 1));
Update (1, dfn[u], dfn[u] + siz[u] - 1, 1);
}
}
return 0;
}