树链刨分:
在一棵树上进行路径的修改,求和,取极值,数据量较小的情况,我们使用LCA就可以,但是数据量大的时候,我们就需要使用线段树了,但是如何使用,这就需要貌似高级的复杂算法-----树链刨分。
简单来说就是将一个树的每一个节点都分配到一个重链中,将这些重链收尾连接,变成线性结构,然后使用线段数进行求解。
介绍几个概念:
记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的重链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),rush[v]表示v处在线性结构的位置。
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
可以通过两个dfs求得重链:
int dfs1(int v,int fas){
fa[v]=fas;
if (fas==0) siz[v]=po[v].size();
else siz[v]=po[v].size()-1;
if (fas==0) dep[v]=1;
else dep[v]=dep[fas]+1;
for (int i=0;i<po[v].size();i++){
if (po[v][i].x!=fas){
vis[po[v][i].z]=po[v][i].x;
dfs1(po[v][i].x,v);
if (son[v]==0||siz[son[v]]<siz[po[v][i].x]){
son[v]=po[v][i].x;
}
}
}
return 0;
}
通过dfs1求得了fa,son,dep,siz;int dfs2(int v,int topp){
rush[v]=++totl;
top[v]=topp;
if (fa[v]==0) num[totl]=0;
else {
for (int i=0;i<po[v].size();i++)
if (po[v][i].x==fa[v]){
num[totl]+=po[v][i].v;
break;
}
}
通过dfs2求得了rush,top;
修改操作:例如将u到v的路径上每条边的权值都加上某值x。
记f1 = top[u],f2 = top[v]。
当f1 <> f2时:不妨设dep[f1] >= dep[f2],修改区间线性结构上的【rush【u】,rush【f1】】区间,并使u = fa[f1]。
当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,修改区间线性结构上的【rush【u】,rush【v】】区间,否则修改完成;
重复上述过程,直到修改完成。
记f1 = top[u],f2 = top[v]。
当f1 <> f2时:不妨设dep[f1] >= dep[f2],修改区间线性结构上的【rush【u】,rush【f1】】区间,并使u = fa[f1]。
当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,修改区间线性结构上的【rush【u】,rush【v】】区间,否则修改完成;
重复上述过程,直到修改完成。
求和、求极值操作:类似修改操作,但是不更新区间值,而是对其求和、求极值。
举个例子:
对于上图当要修改11到10的路径时。
第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
第四次迭代:f1 = f2且u = v,修改结束。
第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
第四次迭代:f1 = f2且u = v,修改结束。
下面是一道树链刨分基础题:

Accept: 129 Submit: 548
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description
有n座城市,由n-1条路相连通,使得任意两座城市之间可达。每条路有过路费,要交过路费才能通过。每条路的过路费经常会更新,现问你,当前情况下,从城市a到城市b最少要花多少过路费。
Input
有多组样例,每组样例第一行输入两个正整数n,m(2 <= n<=50000,1<=m <= 50000),接下来n-1行,每行3个正整数a b c,(1 <= a,b <= n , a != b , 1 <= c <= 1000000000).数据保证给的路使得任意两座城市互相可达。接下来输入m行,表示m个操作,操作有两种:一. 0 a b,表示更新第a条路的过路费为b,1 <= a <= n-1 ; 二. 1 a b , 表示询问a到b最少要花多少过路费。
Output
对于每个询问,输出一行,表示最少要花的过路费。
Sample Input
2 31 2 11 1 20 1 21 2 1
Sample Output
12
解题方法:
由于n与m比较大,所以应该使用类似与线段树的数据结构,但是线段树只能解决线性问题,所以使用树链刨分。通过树链刨分,我们可以解决求树上两个节点之间路径修改,求和的问题。
我的代码:
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define maxn 100005
using namespace std;
struct point {
int x;
int v;
int z;
};
vector <point> po[maxn];
int fa[maxn],siz[maxn],son[maxn],dep[maxn],top[maxn],rush[maxn],totl;
int n,m;
int num[maxn];
int vis[maxn];
int tree[maxn*3];
int dfs1(int v,int fas){
fa[v]=fas;
if (fas==0) siz[v]=po[v].size();
else siz[v]=po[v].size()-1;
if (fas==0) dep[v]=1;
else dep[v]=dep[fas]+1;
for (int i=0;i<po[v].size();i++){
if (po[v][i].x!=fas){
vis[po[v][i].z]=po[v][i].x;
dfs1(po[v][i].x,v);
if (son[v]==0||siz[son[v]]<siz[po[v][i].x]){
son[v]=po[v][i].x;
}
}
}
return 0;
}
int dfs2(int v,int topp){
rush[v]=++totl;
top[v]=topp;
if (fa[v]==0) num[totl]=0;
else {
for (int i=0;i<po[v].size();i++)
if (po[v][i].x==fa[v]){
num[totl]+=po[v][i].v;
break;
}
}
//cout<<v<<" "<<fa[v]<<" "<<totl<<" "<<num[totl]<<endl;
if (son[v]!=0){
dfs2(son[v],topp);
for (int i=0;i<po[v].size();i++){
if (po[v][i].x!=fa[v]&&po[v][i].x!=son[v]){
dfs2(po[v][i].x,po[v][i].x);
}
}
}
return 0;
}
int build (int o,int L,int R){
int M=L+(R-L)/2;
if (L==R) tree[o]=num[L];
else {
build (o*2,L,M);
build (o*2+1,M+1,R);
tree[o]=tree[o*2]+tree[o*2+1];
}
return 0;
}
int yy,d;
int add(int o,int L,int R){
if (L==R&&L==yy){
tree[o]=d;
num[L]=d;
}
else {
int M=L+(R-L)/2;
if (yy<=M) add(o*2,L,M);
if (yy>M) add(o*2+1,M+1,R);
tree[o]=tree[o*2]+tree[o*2+1];
}
return 0;
}
int _sum,y1,y2;
int query(int o,int L,int R){
if (y1<=L&&y2>=R){
_sum+=tree[o];
}
else {
int M=L+(R-L)/2;
if (y1<=M) query(o*2,L,M);
if (y2>M) query(o*2+1,M+1,R);
}
return 0;
}
int print(int v,int u){
int ans=0;
while (v!=u){
int f1=top[v],f2=top[u];
if (f1==f2){
_sum=0;
y1=rush[v];y2=rush[u];
if (y1>y2){
int dddf=y1;
y1=y2;y2=dddf;
}
query(1,1,n);
ans+=_sum;
if (dep[v]>dep[u]) ans-=num[rush[u]];
else ans-=num[rush[v]];
break;
}
else {
if (dep[f1]>dep[f2]){
y1=rush[v];y2=rush[f1];
if (y1>y2){
int dddf=y1;
y1=y2;y2=dddf;
}
_sum=0;
query(1,1,n);
ans+=_sum;
v=fa[f1];
}
else if (dep[f1]<dep[f2]){
y1=rush[u];y2=rush[f2];
if (y1>y2){
int dddf=y1;
y1=y2;y2=dddf;
}
_sum=0;
query(1,1,n);
ans+=_sum;
u=fa[f2];
}
else {
y1=rush[v];y2=rush[f1];
if (y1>y2){
int dddf=y1;
y1=y2;y2=dddf;
}
_sum=0;
query(1,1,n);
ans+=_sum;
v=fa[f1];
y1=rush[u];y2=rush[f2];
if (y1>y2){
int dddf=y1;
y1=y2;y2=dddf;
}
_sum=0;
query(1,1,n);
ans+=_sum;
u=fa[f2];
}
}
}
printf("%d\n",ans);
return 0;
}
int main (){
//freopen("test.in","r",stdin);
while (~scanf("%d%d",&n,&m)){
memset(fa,0,sizeof(fa));
memset(siz,0,sizeof(siz));
memset(son,0,sizeof(son));
memset(dep,0,sizeof(dep));
memset(top,0,sizeof(top));
memset(num,0,sizeof(num));
memset(rush,0,sizeof(rush));
memset(vis,0,sizeof(vis));
for (int i=1;i<=n;i++) po[i].clear();
for (int i=1;i<=n-1;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
point e;e.x=b;e.v=c;e.z=i;
po[a].push_back(e);
e.x=a;
po[b].push_back(e);
}
dfs1(1,0);
totl=0;
dfs2(1,1);
build(1,1,n);
for (int i=0;i<m;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&d);
if (a==0){
yy=rush[vis[b]];
add(1,1,n);
}
else if (a==1){
print(b,d);
}
}
/*cout<<"top ";
for (int i=1;i<=n;i++){
cout<<top[i]<<" ";
}
cout<<endl;
cout<<"rush ";
for (int i=1;i<=n;i++){
cout<<rush[i]<<" ";
}
cout<<endl;
cout<<"num ";
for (int i=1;i<=n;i++){
cout<<num[i]<<" ";
}
cout<<endl;
cout<<"dep ";
for (int i=1;i<=n;i++){
cout<<dep[i]<<" ";
}
cout<<endl;*/
}
return 0;
}