树链剖分笔记

算法思想

第一次DFS

void DFS1(int u,int f)
{
	Size[u]=1;d[u]=d[f]+1;//子树大小,点深度
	son[u]=0;fa[u]=f;//重链节点,父节点
	for(int i=head[u];i;i=e[i].next
	{
		int v=e[i].to;
		if(v==f)continue;
		DFS1(v,u);
		Size[u]+=Size[v];
		if(Size[son[u]]<Size[v]) son[u]=v;
	}
}

第二次DFS

u和f[u]的重子top相同

void DFS2(int u,int topu)
{
	top[u]=topu;
	if(son[u])DFS2(son[u],topu);
	for(int i=head[u];i;i=e[i].next)
		if(e[i].to!=f[u]&&e[i].to!=son[u])
			DFS2(e[i].to,e[i].to);//开启一条新重链
}

先DFS1,后DFS2

树链剖分与LCA

LCA的定义不再赘述,这里给出用树链剖分求出LCA的思路(如图)

  1. 对于给定的节点对(x,y),若x和y在同一条重链中,LCA为深度较小点
  2. 否则将top深度较大的点往上跳到top的父亲,这一步跳过了一条轻边
  3. 重复第二步直到x和y在一条重链中

在这里插入图片描述代码

int LCA(int x,int y)
{
	 while(top[x]!=top[y])//不在同一重链时
	 {
	 	if(d[top[x]]<d[top[y]])//确保x的top深度更大
	 		swap(x,y);
	 	x=f[top[x]];//x往上跳过一条轻边
	 } 	
	 return d[x]<d[y]?x:y;//返回深度小的
}

关于该算法的时间复杂度

最坏情况时,x和y的LCA为根节点,且不在一条重链上,需要将x,y到根路径上的所有轻边跳一次,但任何一个点到根路径上的重链数量和轻边数量都不超过 O ( log ⁡ n ) O(\log n) O(logn),一般情况下上界不满,所以 O ( log ⁡ n ) O(\log n) O(logn)常数很小

不超过 O ( log ⁡ n ) O(\log n) O(logn)的证明:
若y为x轻子,存在一个x的重子z满足Size[z]≥Size[y]
Size[z]+Size[y]<Size[x]→2Size[y]<Size[x]
每次经过一条轻边Size至少除以2,所以最多经过 O ( log ⁡ n ) O(\log n) O(logn)条轻边

树链剖分与线段树

以下面的题面为例

给定一棵n个点的树,根节点为1,每个点有点权,m次操作,操作有以下四种:
1 x y z 将x~y路径上每个点点权加z
2 x z 将x子树内每个点点权加z
3 x y 查询x到y路径上每个点点权之和
4 x 查询子树内每个点点权之和
1≤n,m≤100000

关于树链剖分转线段树
树链剖分中,第二次DFS先DFS重子,再DFS轻子,将依次DFS到的点记录下来,则每条重链是一个连续区间,每个点的子树也是一个连续区间,对于子树操作,便可以转换成区间操作,可以用线段树维护,对于任意路径操作,要么在同一条重链,要么可以分解成最多log条重链+轻边,同样可以转化成区间操作,子树操作时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),树链剖分完成后,每条重链相当于一段连续区间

加上线段树之后的DFS

void DFS2(int u,int topu)
{
	top[u]=topu;
	Nid[u]=++dfn;//原始u对应的DFS序新编号是Nid[x]
	Oid[dfn]=u;//新编号Nid[u]对应的老编号为u,即Oid[Nid[u]]=u;
	if(son[u])DFS2(son[u],topu);//先处理重边,确保重链区间连续
	for(int i=head[u];i;i=e[i].next)
		if(e[i].to!=f[u]&&e[i].to!=son[u])
			DFS2(e[i].to,e[i].to);
}//如果对u为根的整个子树操作,对应的区间[Nid[u]][Nid[u]+Size[u]-1]

链处理模板

 void chain(int x,int y)
 {
 	while(top[x]!=top[y])
 	{
 		if(d[top[x]]<d[top[y]])swap(x,y);
 		//具体处理top[x]到x这一段,比如修改该段节点值
 		x=f[top[x]];
 	}
 	if(d[x]>d[y])swap(x,y);
 	//具体处理x~y这一段,比如修改该段节点值
 }

具体例子如图(图来自LCY算法培训,侵删),每个节点保存DFS序的左右端点以及端点边权

在这里插入图片描述

训练

HDU2586

题目大意:在学LCA的时候已经做过,略,只讲解树链剖分的做法

思路:基本用上述已经写过的思想,注意注释地方的细节

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e6;
int T,n,m,cnt,head[maxn],d[maxn],son[maxn],dis[maxn],f[maxn],Size[maxn],top[maxn];
struct node {
    int to,next,w;
} e[maxn];
void Add(int to,int from,int val) {//链式前向星存边
    e[++cnt].to=to;
    e[cnt].w=val;
    e[cnt].next=head[from];
    head[from]=cnt;
}
void DFS1(int u,int fa) {//第一次DFS
    Size[u]=1;
    f[u]=fa;
    d[u]=d[fa]+1;
    son[u]=0;//以上都为初始化
    for(int i=head[u]; i; i=e[i].next)
        if(e[i].to!=fa) {
            dis[e[i].to]=dis[u]+e[i].w;//必须先构造长度,不然下一个节点无值可用
            DFS1(e[i].to,u);
            Size[u]+=Size[e[i].to];
            if(Size[e[i].to]>Size[son[u]])//找重链
                son[u]=e[i].to;
        }
}
void DFS2(int u,int topu) {
    top[u]=topu;
    if(son[u])DFS2(son[u],topu);
    for(int i=head[u]; i; i=e[i].next)
        if(e[i].to!=f[u]&&e[i].to!=son[u])
            DFS2(e[i].to,e[i].to);
}
int LCA(int x,int y) {
    while(top[x]!=top[y]) {
        if(d[top[x]]<d[top[y]])
            swap(x,y);
        x=f[top[x]];//关键点,跳边
    }
    return d[x]<d[y]?x:y;
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cin >>T;
    while(T--) {
        cin >>n>>m;
        for(int i=0; i<n-1; i++) {
            int u,v,k;
            cin >>u>>v>>k;
            Add(u,v,k);
            Add(v,u,k);
        }
        DFS1(1,0);
        DFS2(1,1);//重链初始为自己,第二个参数必须与第一个相同
        while(m--) {
            int u,v;
            cin >>u>>v;
            cout <<dis[u]+dis[v]-2*dis[LCA(u,v)]<<endl;
        }
        cnt=0;
        memset(head,0,sizeof(head));
        memset(Size,0,sizeof(Size));
        memset(dis,0,sizeof(dis));
    }
    return 0;
}

POJ2763

题目大意:一棵树,执行两种操作,修改某边边权或者求初始点到某点的距离,并且将初始点更改为该点,输出距离

思路:由于本题为单点修改和区间求和,可以先树链剖分,之后选择树状数组/线段树求解

关于代码中的 acc+=Getsum(Nid[v])-Getsum(Nid[son[u]]-1);

这一行自我感觉有些难理解,遂画图来说明,如图,u和v已经在一条重链上,现在需要求u~v的和,代码中的son[u]为u的子节点编号,Nid[son[u]]-1又回退到了u,此时可以直接带入求出前缀和

在这里插入图片描述

PS: 本题卡了加速的cin和cout,浪费了我两个小时,我*你先人,cin/cout,狗都不用!

代码(树状数组)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <queue>
//#include <unordered_map>
#include <map>
#include <set>
#include <numeric>
#include <stack>
#include <sstream>
#include <cmath>
#include <bitset>
//#include <unordered_set>
#include <functional>
#include <list>
#include <vector>
#include <iterator>
using namespace std;
typedef long long ll;
const int maxn=4e6+10;
int head[maxn],cnt,n,q,s,value[maxn];
//前向星头,边计数器,点数,询问数,起点,树状数组
int son[maxn],d[maxn],Nid[maxn],Oid[maxn],dfn,Size[maxn],f[maxn],top[maxn],fid[maxn],w[maxn];
//重子编号,节点深度,DFS序对应的编号,DFS序编号计数器,父节点编号,重链编号,边对应点,边权
struct node {
    int to,next,id;
} e[maxn];
void Update(int pos,int t) {
    for(; pos<=dfn; pos+=-pos&pos)
        value[pos]+=t;
}
int Getsum(int pos) {
    int sum=0;
    for(; pos>0; pos-=-pos&pos)
        sum+=value[pos];
    return sum;
}
void DFS1(int u,int fa) {
    f[u]=fa;
    Size[u]=1;
    d[u]=d[fa]+1;//记录层数
    son[u]=0;
    for(int i=head[u]; i>0; i=e[i].next) {
        int v=e[i].to;
        if(v!=fa) {
            fid[e[i].id]=v;//边联系点
            DFS1(v,u);
            Size[u]+=Size[v];//统计子树大小
            if(Size[v]>Size[son[u]])//找重链
                son[u]=v;
        }
    }
}
void DFS2(int u,int topu) {
    top[u]=topu;
    Nid[u]=++dfn;//记录DFS序
    Oid[dfn]=u;
    if(son[u])DFS2(son[u],topu);//重链
    for(int i=head[u]; i>0; i=e[i].next) {
        int v=e[i].to;
        if(v!=f[u]&&v!=son[u])
            DFS2(v,v);
    }
}
void Add(int to,int from,int val) {
    e[++cnt].to=to;
    e[cnt].id=val;
    e[cnt].next=head[from];
    head[from]=cnt;
}
int Sum(int u,int v) {
    int acc=0;
    while(top[u]!=top[v]) {
        if(d[top[u]]<d[top[v]])
            swap(u,v);
        acc+=Getsum(Nid[u])-Getsum(Nid[top[u]]-1);//统计节点到重链点的和,这里是和线段树反过来的,由于是前缀和,所以需要-1
        u=f[top[u]];
    }
    if(u==v)return acc;
    if(d[u]>d[v])
        swap(u,v);
    acc+=Getsum(Nid[v])-Getsum(Nid[son[u]]-1);
    return acc;
}
int main() {
//    ios::sync_with_stdio(0),cin.tie();
//    cin >>n>>q>>s;
    scanf("%d%d%d",&n,&q,&s);
    for(int i=1; i<n; i++) {
        int a,b;
        //cin >>a>>b>>w[i];
        scanf("%d%d%d",&a,&b,&w[i]);
        Add(a,b,i);
        Add(b,a,i);
    }
    DFS1(1,0);
    DFS2(1,1);//树链剖分
    for(int i=1; i<n; i++)//构造树状数组
        Update(Nid[fid[i]],w[i]);
    while(q--) {
        int choice,x,y;
        //cin >>choice;
        scanf("%d",&choice);
        if(choice) {
            //cin >>x>>y;
            scanf("%d%d",&x,&y);
            Update(Nid[fid[x]],y-w[x]);
            w[x]=y;
        } else {
//            cin >>x;
//            cout <<Sum(s,x)<<endl;
            scanf("%d",&x);
            printf("%d\n",Sum(s,x));
            s=x;
        }
    }
    return 0;
}
/*
6 100 1
1 2 1
2 6 2
1 3 3
3 4 6
4 5 7
0 6
*/

POJ3237

题目大意:给出n个节点的树,编号1~N,边编号1 ~N-1,有边权,三种操作,更改边权、置a ~b所有路径相反数、求出a ~b最大边长

思路:先树链剖分,之后直接线段树,由于是按边序号修改点权的,必须建立边编号<->点编号的索引,具体见代码

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
const int INF=2000000000;//无穷大
int head[maxn],sum=1,cnt,N,d[maxn],son[maxn],Size[maxn];
//链式前向星头,边计数,dfs序计数器,节点数,深度,重链,树大小
int f[maxn],top[maxn],w[maxn],a[maxn],rk[maxn];
//父节点,重链编号,DFS序索引,点权
struct node {
    int u,v,w,nt;
} e[maxn];
struct tree {
    int mx,mn;
    bool lazy;
} Seg[maxn<<2];
void PushUp(int rt) {
    Seg[rt].mn=min(Seg[rt<<1].mn,Seg[rt<<1|1].mn);
    Seg[rt].mx=max(Seg[rt<<1].mx,Seg[rt<<1|1].mx);
}
void PushDown(int rt) {
    if(Seg[rt].lazy) {
        Seg[rt<<1].lazy^=1;
        Seg[rt<<1|1].lazy^=1;
        swap(Seg[rt<<1].mx,Seg[rt<<1].mn);
        Seg[rt<<1].mx*=-1;
        Seg[rt<<1].mn*=-1;
        swap(Seg[rt<<1|1].mx,Seg[rt<<1|1].mn);//容易错
        Seg[rt<<1|1].mx*=-1;
        Seg[rt<<1|1].mn*=-1;
        Seg[rt].lazy=0;
    }
}
void Add(int u,int v,int w) {
    e[++sum].u=u;//由于边有编号,每次修改的是一条边,需要存两个端点
    e[sum].v=v;
    e[sum].w=w;
    e[sum].nt=head[u];
    head[u]=sum;
}
int Query(int L,int R,int l,int r,int rt) {
    if(l>r||L>r||R<l)//越界返回极小值
        return -INF;
    if(L<=l&&R>=r)
        return Seg[rt].mx;
    int mid=(l+r)>>1;
    PushDown(rt);
    return max(Query(L,R,l,mid,rt<<1),Query(L,R,mid+1,r,rt<<1|1));
}
void Update1(int pos,int val,int l,int r,int rt) {
    if(l==r) {
        Seg[rt].mn=val;
        Seg[rt].mx=val;
        return;
    }
    PushDown(rt);
    int mid=(l+r)>>1;
    if(pos<=mid)Update1(pos,val,l,mid,rt<<1);
    if(pos>mid)Update1(pos,val,mid+1,r,rt<<1|1);
    PushUp(rt);
}
void Update2(int L,int R,int l,int r,int rt) {
    if(L<=l&&r<=R) {
        swap(Seg[rt].mn,Seg[rt].mx);
        Seg[rt].mn*=-1;
        Seg[rt].mx*=-1;
        Seg[rt].lazy^=1;
        return ;
    }
    int mid=(l+r)>>1;
    PushDown(rt);
    if(L<=mid)Update2(L,R,l,mid,rt<<1);
    if(mid<R)Update2(L,R,mid+1,r,rt<<1|1);
    PushUp(rt);
}
void DFS1(int u,int fa) {
    d[u]=d[fa]+1;//记录层数
    Size[u]=1;//规模
    son[u]=0;//重链
    f[u]=fa;//记录父节点
    for(int i=head[u]; i; i=e[i].nt) {
        int v=e[i].v;
        if(v==fa)continue;
        a[v]=e[i].w;//将该边边权存入子节点,重要
        DFS1(v,u);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])son[u]=v;//获得重链
    }
}
void DFS2(int u,int topu) {
    top[u]=topu;
    w[u]=++cnt;//记录dfs序
    rk[cnt]=u;//dfs序对应原节点编号,重要
    if(son[u])DFS2(son[u],topu);//重链
    for(int i=head[u]; i; i=e[i].nt) {
        int v=e[i].v;
        if(v==f[u]||v==son[u])
            continue;
        DFS2(v,v);//开一条新重链
    }
}
void Build(int l,int r,int rt) {
    Seg[rt].lazy=0;//初始化标记
    if(l==r) {
        Seg[rt].mn=Seg[rt].mx=a[rk[l]];//获得dfs序对应的原节点的值
        return;
    }
    int mid=(l+r)>>1;
    Build(l,mid,rt<<1);
    Build(mid+1,r,rt<<1|1);
    PushUp(rt);
}
int AskMax(int u,int v) {//求得最值
    int res=-INF;
    while(top[u]!=top[v]) {
        if(d[top[u]]<d[top[v]])
            swap(u,v);
        res=max(res,Query(w[top[u]],w[u],1,N,1));
        u=f[top[u]];
    }
    if(d[u]>d[v])swap(u,v);
    return max(res,Query(w[u]+1,w[v],1,N,1));
}
void Negate(int u,int v) {
    while(top[u]!=top[v]) {
        if(d[top[u]]<d[top[v]])
            swap(u,v);
        Update2(w[top[u]],w[u],1,N,1);
        u=f[top[u]];
    }
    if(d[u]>d[v])swap(u,v);
    Update2(w[u]+1,w[v],1,N,1);
}
int main() {
    int t;
    scanf("%d",&t);
    while(t--) {
        cnt=0;
        sum=1;//从1开始方便后面直接取边
        memset(head,0,sizeof(head));//初始化
        scanf("%d",&N);
        for(int i=1; i<N; i++) {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);//存边
            Add(u,v,w);
            Add(v,u,w);
        }
        scanf("\n");
        DFS1(1,0);
        DFS2(1,1);
        Build(1,N,1);
        char s[10];
        while(scanf("%s",s)&&s[0]!='D') {
            int x,y;
            scanf("%d%d\n",&x,&y);
            if(s[0]=='Q')
                printf("%d\n",AskMax(x,y));
            else if(s[0]=='C') {
                int u=e[x*2].u,v=e[x*2].v;//sum为1在这里方便直接取
                if(d[u]<d[v])Update1(w[v],y,1,N,1);
                else Update1(w[u],y,1,N,1);
            } else
                Negate(x,y);
        }
    }
    return 0;
}

HDU3966

题目大意:一棵树,N个节点,有点权,两种操作:a ~ b路径上所有点都加上一个值;询问某点的点权

思路:树链剖分模板,唯一注意的就是点权的赋值,点编号与dfs序的对应

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <unordered_map>
#include <map>
#include <set>
#include <numeric>
#include <stack>
#include <sstream>
#include <cmath>
#include <bitset>
#include <unordered_set>
#include <functional>
#include <list>
#include <vector>
#include <iterator>
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
int head[maxn],cnt,N,M,P,camp[maxn];
//链式前向星头,边计数,点数,边数,操作数,节点值
int Size[maxn],f[maxn],son[maxn],d[maxn],top[maxn];
//子树规模,父节点,重链节点,深度,重链节点
int Nid[maxn],dfn,Oid[maxn];
struct tree {
    int val,lazy;
} Seg[maxn];
struct node {
    int to,next;
} e[maxn];
void Add(int to,int from) {
    e[++cnt].to=to;
    e[cnt].next=head[from];
    head[from]=cnt;
}
void DFS1(int u,int fa) {
    f[u]=fa;
    Size[u]=1;
    son[u]=0;
    d[u]=d[fa]+1;
    for(int i=head[u]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==fa)continue;
        DFS1(v,u);
        Size[u]+=Size[v];
        if(Size[v]>Size[son[u]])son[u]=v;
    }
}
void DFS2(int u,int topu) {
    Nid[u]=++dfn;
    top[u]=topu;
    Oid[dfn]=u;
    if(son[u])DFS2(son[u],topu);
    for(int i=head[u]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==f[u]||v==son[u])continue;
        DFS2(v,v);
    }
}
void PushDown(int l,int r,int rt) {
    if(Seg[rt].lazy!=0) {
        Seg[rt<<1].lazy+=Seg[rt].lazy;
        Seg[rt<<1|1].lazy+=Seg[rt].lazy;
        Seg[rt<<1].val+=l*Seg[rt].lazy;
        Seg[rt<<1|1].val+=r*Seg[rt].lazy;
        Seg[rt].lazy=0;
    }
}
void Build(int l,int r,int rt) {
    Seg[rt].lazy=0;//区间节点的懒标记也要更新
    if(l==r) {
        Seg[rt].val=camp[Oid[l]];//这个地方容易错,l是dfs的序号,需要将序号还原成节点的序号
        return;
    }
    int mid=(l+r)>>1;
    Build(l,mid,rt<<1);
    Build(mid+1,r,rt<<1|1);
    Seg[rt].val=Seg[rt<<1].val+Seg[rt<<1|1].val;
}
void Update(int L,int R,int v,int l,int r,int rt) {
    if(l>r||l>R||L>r)
        return ;
    if(L<=l&&R>=r) {
        Seg[rt].val+=(r-l+1)*v;
        Seg[rt].lazy+=v;
        return;
    }
    int mid=(l+r)>>1;
    PushDown(mid-l+1,r-mid,rt);
    Update(L,R,v,l,mid,rt<<1);
    Update(L,R,v,mid+1,r,rt<<1|1);
    Seg[rt].val=Seg[rt<<1].val+Seg[rt<<1|1].val;
}
int Query(int pos,int l,int r,int rt) {
    if(l==r&&pos==l)
        return Seg[rt].val;
    int mid=(l+r)>>1;
    PushDown(mid-l+1,r-mid,rt);
    if(pos<=mid)return Query(pos,l,mid,rt<<1);
    else return Query(pos,mid+1,r,rt<<1|1);
}
void chain(int u,int v,int w) {
    while(top[u]!=top[v]) {
        if(d[top[u]]<d[top[v]])
            swap(u,v);
        Update(Nid[top[u]],Nid[u],w,1,N,1);//重链节点到当前节点更新
        u=f[top[u]];
    }
    if(d[u]>d[v])
        swap(u,v);
    Update(Nid[u],Nid[v],w,1,N,1);//在同一条重链上选择深度小到深度大
}
int main() {
    while(scanf("%d%d%d",&N,&M,&P)!=EOF) {
        for(int i=1; i<=N; i++)
            scanf("%d",&camp[i]);
        for(int i=1; i<=M; i++) {
            int u,v;
            scanf("%d%d",&u,&v);
            Add(u,v);
            Add(v,u);
        }
        DFS1(1,0);
        DFS2(1,1);
        Build(1,N,1);
        while(P--) {
            char s[2];
            int x,y,z;
            scanf("%s",s);
            switch(s[0]) {
            case'I':
                scanf("%d%d%d",&x,&y,&z);
                chain(x,y,z);
                break;
            case'D':
                    scanf("%d%d%d",&x,&y,&z);
                chain(x,y,-z);//链更新
                break;
            case'Q':
                scanf("%d",&x);
                printf("%d\n",Query(Nid[x],1,N,1));//查询
                break;
            }
        }
        cnt=dfn=0;
        memset(head,0,sizeof(head));
    }
    return 0;
}

HDU5221

题目大意:一棵树,有点权,N个节点,三种操作:选取a ~ b路径上的所有点作为已收纳;更改一个顶点状态为未收纳(如果一开始就未收纳则不变);将以x为根的所有子树都收纳(包括x)。输出每次操作后的已收纳顶点的点权和

思路:一开始尝试标记+线段树记录区间和,但是超时,参考了网上其他的代码后发现了更高效的做法,构造线段树时不构造区间和,只有进行收纳和更改操作时才利用前缀和将对应的区间更新求和,使用标记防止不必要的更新,最后根节点的和为所有已收纳节点的和,选用的数据结构为线段树,树状数组应该也可以,之后有时间会尝试写一下
代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int head[maxn],cnt,N,T,Q,val[maxn],pre[maxn];
//链式前向星头,计数器,点个数,例子数,操作数,前缀和
int dfn,d[maxn],Size[maxn],son[maxn],f[maxn],top[maxn],id[maxn],rk[maxn];
//dfs序计数器,深度,规模,重链节点,父,重链编号,dfs序编号,索引
struct node {
    int to,next,w;
} e[maxn];
void Add(int to,int from) {//链式前向星加边
    e[++cnt].to=to;
    e[cnt].next=head[from];
    head[from]=cnt;
}
void DFS1(int u,int fa) {
    Size[u]=1;
    son[u]=0;
    f[u]=fa;
    d[u]=d[fa]+1;
    for(int i=head[u]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==fa)continue;
        DFS1(v,u);
        Size[u]+=Size[v];
        if(Size[son[u]]<Size[v])son[u]=v;
    }
}
void DFS2(int u,int topu) {
    top[u]=topu;
    id[u]=++dfn;
    rk[dfn]=u;
    pre[dfn]=pre[dfn-1]+val[u];//获得前缀和,重要
    if(son[u])DFS2(son[u],topu);
    for(int i=head[u]; i; i=e[i].next) {
        int v=e[i].to;
        if(v==f[u]||v==son[u])continue;
        DFS2(v,v);
    }
}
struct tree {
    int sum;
    bool type;//区间更新标记
} Seg[maxn];
void PushUp(int rt) {//向上回推
    Seg[rt].sum=Seg[rt<<1].sum+Seg[rt<<1|1].sum;
}
void PushDown(int l,int r,int rt) {
    if(Seg[rt].type) {
        Seg[rt<<1].type=Seg[rt<<1|1].type=1;
        Seg[rt].type=0;
        int mid=(l+r)>>1;
        Seg[rt<<1].sum=pre[mid]-pre[l-1];
        Seg[rt<<1|1].sum=pre[r]-pre[mid];
    }
}
void Build(int l,int r,int rt) {//清空线段树的标记和区间和
    Seg[rt].type=0;
    Seg[rt].sum=0;
    if(l==r)
        return;
    int mid=(l+r)>>1;
    Build(l,mid,rt<<1);
    Build(mid+1,r,rt<<1|1);
}
void Update1(int pos,int l,int r,int rt) {//单点更新
    if(l>r||r<pos||pos<l)
        return ;
    if(l==r&&l==pos) {//将该点的权值置为零
        Seg[rt].sum=0;
        return;
    }
    int mid=(l+r)>>1;
    PushDown(l,r,rt);
    Update1(pos,l,mid,rt<<1);
    Update1(pos,mid+1,r,rt<<1|1);
    PushUp(rt);
}
void Update2(int L,int R,int l,int r,int rt) {//区间更新
    if(l>r||r<L||R<l)
        return ;
    if(l>=L&&R>=r) {
        Seg[rt].type=1;
        Seg[rt].sum=pre[r]-pre[l-1];//通过前缀和构造区间和
        return;
    }
    int mid=(l+r)>>1;
    PushDown(l,r,rt);
    Update2(L,R,l,mid,rt<<1);
    Update2(L,R,mid+1,r,rt<<1|1);
    PushUp(rt);
}
int Query(int l,int r,int rt) {//查询,其实该函数只运行了一次,因为先前的更新已经使得1~N记录了有效区间和
    if(Seg[rt].sum)//如果有值,直接返回
        return Seg[rt].sum;
    else
        return 0;
    int mid=(l+r)>>1;
    PushDown(l,r,rt);
    return Query(l,mid,rt<<1)+Query(mid+1,r,rt<<1|1);
}
void chain(int u,int v) {//进行链操作
    while(top[u]!=top[v]) {
        if(d[top[u]]<d[top[v]])
            swap(u,v);
        Update2(id[top[u]],id[u],1,N,1);
        u=f[top[u]];
    }
    if(d[u]>d[v])
        swap(u,v);
    Update2(id[u],id[v],1,N,1);
}
int main() {
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&N);
        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);
            Add(u,v);
            Add(v,u);
        }
        DFS1(1,0);
        DFS2(1,1);
        Build(1,N,1);
        scanf("%d",&Q);
        while(Q--) {
            int a,b,c;
            scanf("%d%d",&a,&b);
            switch(a) {
            case 1:
                scanf("%d",&c);
                chain(b,c);
                break;
            case 2:
                Update1(id[b],1,N,1);
                break;
            case 3:
                Update2(id[b],id[b]+Size[b]-1,1,N,1);
                break;
            }
            printf("%d\n",Query(1,N,1));
        }
        memset(head,0,sizeof(head));
        memset(pre,0,sizeof(pre));
        cnt=dfn=0;
    }
    return 0;
}

总结

树链剖分多用于LCA以及各种关于树上路径的操作(更新,查找),常与子树或路径操作一同使用,常转化为线段树的区间操作,在写代码的时候有几个需要注意的常错点

  1. 多组样例输入时,需要对线段树进行清空,每个节点都需要清空标记和值
  2. PushDown函数中容易写错位运算,需要注意
  3. 链操作时需要注意范围,树状数组是求前缀和是需要-1的
  4. 初始化线段树时,预值应当为当前dfs编号对应的原节点编号的值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值