树链剖分本质上就是将一棵树拆分为多条树链,并按照一定的规则进行有规律的存储,简化我们树上的操作。
几个定义:
size(k) : 以k为根节点时,k的子树的节点个数。
重儿子:u的子节点中size()最大的点
轻儿子:除了重儿子,剩余的节点都为轻儿子
重边:若根节点为u,u的重儿子为v,则(u,v)为重边
轻边:除了重边,剩余的边都为轻边
重链:链上的所有边都为重边
需要计算的值:
fa[u]:u的父亲
dep[u]:u节点的深度
size[u]:以u为根节点的子树的节点数
son[u]:u的重儿子
top[u]:u所在的重链的顶点
tid[u]:剖分后节点的新id
rk[u]:新节点id对应在原树中的节点
DFS1 : 计算size,dep,fa,son数组
void DFS1(int now, int from, int deep) // 寻找重边
{
dep[now] = deep;
fa[now] = from;
siz[now] = 1;
int length = es[now].size();
for(int i = 0 ; i < length ; i++)
{
int v = es[now][i];
if(v != from)
{
DFS1(v, now, deep + 1);
siz[now] += siz[v];
if(son[now] == -1 || siz[v] > siz[son[now]])
{
son[now] = v;
}
}
}
}
DFS2 :将重边链接成重链(一个点也算重链)
void DFS2(int now, int tp) // 重边链接成重链
{
top[now] = tp;
tid[now] = ++tim; // now 在 线段树 上的编号
rk[tid[now]] = now;
if(son[now] == -1) return;
DFS2(son[now],tp);
int length = es[now].size();
for(int i = 0 ; i < length ; i++)
{
int v = es[now][i];
if(v != son[now] && v != fa[now]) DFS2(v,v);
}
}
得到了以上的结果之后,对于树上路径的操作就变得很容易了,其实树上路径修改就是不断找LCA的过程,如果两点的top相等说明两点在同一条重链上,直接更新区间 【min(tid[y], tid[x]),max(tid[y], tid[x])】 , 如果不在同一条重链上,那么他们的LCA一定不会出现在top较大的重链上,那么不断更新【tid[top[now]] ,tid[now]】,之到当前两点的top相等
void Change(int x, int y, int val)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
Update(tid[top[x]], tid[x], val, 1, n, 1);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
Update(tid[y], tid[x], val, 1, n, 1);
}
以HDU3966【书上路径整体更新 + 单点查询】为例
#include <bits/stdc++.h>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int maxn = 100000+10;
int num[maxn];
int n , m , Q;
//树链剖分
int siz[maxn], top[maxn], son[maxn];
int dep[maxn], fa[maxn], tid[maxn], seg_pos[maxn];
int tim;
vector<int>es[maxn];
void Init()
{
for(int i = 0 ; i < maxn ; i++)
{
es[i].clear();
}
tim = 0;
memset(son, -1, sizeof(son));
}
void DFS1(int now, int from, int deep) // 寻找重边
{
dep[now] = deep;
fa[now] = from;
siz[now] = 1;
int length = es[now].size();
for(int i = 0 ; i < length ; i++)
{
int v = es[now][i];
if(v != from)
{
DFS1(v, now, deep + 1);
siz[now] += siz[v];
if(son[now] == -1 || siz[v] > siz[son[now]])
{
son[now] = v;
}
}
}
}
void DFS2(int now, int tp) // 重边链接成重链
{
top[now] = tp;
tid[now] = ++tim; // now 在 线段树 上的编号
seg_pos[tid[now]] = now;
if(son[now] == -1) return;
DFS2(son[now],tp);
int length = es[now].size();
for(int i = 0 ; i < length ; i++)
{
int v = es[now][i];
if(v != son[now] && v != fa[now]) DFS2(v,v);
}
}
//线段树
int sum[4*maxn],col[4*maxn];
void PushUP(int rt)
{
sum[rt]=max(sum[rt<<1],sum[rt<<1|1]);
}
void PushDown(int rt,int m)
{
if(col[rt])
{
col[rt<<1]+=col[rt];
col[rt<<1|1]+=col[rt];
sum[rt<<1]+=(m-(m>>1))*col[rt];
sum[rt<<1|1]+=(m>>1)*col[rt];
col[rt]=0;
}
}
void Build(int l,int r,int rt)
{
col[rt]=0;
if(l==r)
{
sum[rt]=num[seg_pos[l]];
return;
}
int mid=(l+r)>>1;
Build(lson);
Build(rson);
PushUP(rt);
}
void Update(int L,int R,int v,int l,int r,int rt)
{
if(L<=l&&R>=r)
{
col[rt]+=v;
sum[rt]+=v*(r-l+1);
return;
}
PushDown(rt,r-l+1);
int mid=(l+r)>>1;
if(L<=mid)
Update(L,R,v,lson);
if(R>mid)
Update(L,R,v,rson);
PushUP(rt);
}
int Query(int l,int r,int rt,int val)
{
if(l==r)
return sum[rt];
PushDown(rt,r-l+1);
int mid=(l+r)>>1;
int ret=0;
if(val<=mid) ret=Query(lson,val);
else ret=Query(rson,val);
PushUP(rt);
return ret;
}
void Change(int x, int y, int val)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
Update(tid[top[x]], tid[x], val, 1, n, 1);
x = fa[top[x]];
}
if(dep[x] < dep[y]) swap(x,y);
Update(tid[y], tid[x], val, 1, n, 1);
}
int main()
{
ios::sync_with_stdio(false);
char type;
int a,b,c;
while(cin >> n >> m >> Q)
{
Init();
for(int i=1; i<=n; i++) cin >> num[i];
for(int i=1; i<=m; i++)
{
cin >> a >> b;
es[a].push_back(b);
es[b].push_back(a);
}
DFS1(1,0,0);
DFS2(1,1);
Build(1,n,1);
while(Q--)
{
cin >> type;
if(type=='Q')
{
cin >> a;
cout << Query(1,n,1,tid[a]) << endl;
}
else
{
cin >> a >> b >> c;
if(type=='D') c=-c;
Change(a,b,c);
}
}
}
return 0;
}