概念:
重儿子:父亲节点的所有儿子中子树结点最多(size[x])的节点
轻儿子:父亲节点中除了重儿子以外的儿子
重边:父亲结点和重儿子连成的边
轻边:父亲节点和轻儿子连成的边
重链:由多条重边连接而成的路径
轻链:由多条轻边连接而成的路径
dep[x]:x的深度
size[x]:以x为根的子树节点个数总和
id[x]:dfs序所访问的编号
fa[x]:保存u的父亲节点
son[x]:保存重儿子
rk[x]:保存当前dfs序标号在树中所对应的节点
top[x]:保存重链的顶点
性质:
1,如果(u, v)是一条轻边,那么size(v) < size(u)/2;
2,从根结点到任意结点的路所经过的轻重链的个数必定都小于logn;
3, 若求以x为根的子树和,那么子树节点的编号为[id[x],id[x]+size[x]-1]
4, 为何可以运用到线段树,因为dfs序使得重链或轻链上的序号都是有顺序的
时间复杂度:
树链剖分的时间复杂度为 O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1000;
struct edge
{
int next,to;
}e[N<<2];
struct node
{
int l,r,w,siz,lazy;
}tr[N<<2];
int idx,root,MOD,cnt=0,n,m;
int a[N];
int dep[N],fa[N],son[N],size[N],top[N],id[N],rk[N];
int h[N];
//深度 ,父节点,重链儿子节点, ,重链的顶点,每个节点编号 ,编号对应的节点
void add(int u,int v)
{
e[++idx].to=v;e[idx].next=h[u];h[u]=idx;
}
void dfs1(int u,int father,int depth )//处理深度,父亲节点,u为根的节点个数
{
dep[u]=depth;fa[u]=father;size[u]=1;
int maxn=-1;
for(int i=h[u];~i;i=e[i].next)
{
int j=e[i].to;
if(j==father) continue;
dfs1(j,u,depth+1);
size[u]+=size[j];
if(size[j]>maxn) //重链的儿子
{
maxn=size[j];
son[u]=j;
}
}
}
void dfs2(int u,int v)
{
top[u]=v;//标记重链的顶点
id[u]= ++cnt;//节点对应编号
rk[cnt]=a[u];//编号对应节点建树的关键一员
if(!son[u]) return ;
dfs2(son[u],v);
for(int i=h[u];~i;i=e[i].next)
{
int j=e[i].to;
if(j!=son[u]&&j!=fa[u]) dfs2(j,j);//非重链的顶点就是自己
}
}
void push_up(int u)
{
tr[u].w=(tr[u<<1].w+tr[u<<1|1].w+MOD)%MOD;
}
void build(int u,int l,int r)
{
tr[u].l=l;tr[u].r=r;tr[u].siz=r-l+1;
if(l==r)
{
tr[u].w=rk[l];
return ;
}
int mid=l+r>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
push_up(u);
}
void push_down(int u)//
{
if(tr[u].lazy)
{
tr[u<<1].w=(tr[u<<1].w+tr[u<<1].siz*tr[u].lazy)%MOD;
tr[u<<1|1].w=(tr[u<<1|1].w+tr[u<<1|1].siz*tr[u].lazy)%MOD;
tr[u<<1].lazy=(tr[u<<1].lazy+tr[u].lazy)%MOD;
tr[u<<1|1].lazy=(tr[u<<1|1].lazy+tr[u].lazy)%MOD;
tr[u].lazy=0;
}
}
void Interadd(int u,int l,int r,int val)//
{
if(l<=tr[u].l&&tr[u].r<=r)
{
tr[u].w+=tr[u].siz*val;
tr[u].lazy+=val;
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) Interadd(u<<1,l,r,val);
if(r>mid) Interadd(u<<1|1,l,r,val);
push_up(u);
}
void treeadd(int x,int y,int val)//
{
while(top[x]!=top[y])//将两个节点跳到同一重链上
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
Interadd(1,id[top[x]],id[x],val);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);//节点编号的顺序依照深度大小,深度越大节点编号越大
Interadd(1,id[x],id[y],val);//因为遍历线段树区间[l,r]必须从小到大
}
int Insum(int u,int l,int r)//
{
int ans=0;
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].w;
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) ans=(ans+Insum(u<<1,l,r))%MOD;
if(r>mid) ans=( ans+Insum(u<<1|1,l,r))%MOD;
return ans;
}
void querysum(int x,int y)//
{
int ans=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ans=(ans+Insum(1,id[top[x]],id[x]))%MOD;
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
ans=(ans+Insum(1,id[x],id[y]))%MOD;
printf("%d\n",ans);
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d %d %d %d",&n,&m,&root,&MOD);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n-1;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add(x,y);add(y,x);
}
dfs1(root,0,1);
dfs2(root,root);
build(1,1,n);
while(m--)
{
int op,x,y,z;
cin>>op;
if(op==1)
{
scanf("%d %d %d",&x,&y,&z); z=z%MOD;
treeadd(x,y,z);
}
else if(op==2)
{
scanf("%d %d",&x,&y);
querysum(x,y);
}
else if(op==3)
{
scanf("%d %d",&x,&y);
Interadd(1,id[x],size[x]+id[x]-1,y%MOD);
}
else if(op==4)
{
scanf("%d",&x);
printf("%d\n",Insum(1,id[x],id[x]+size[x]-1));
}
}
}