1.前言:
以前初学者都会认为树链剖分是个很难的算法,其实很简单.大致思想就是把树分成链,然后用线段树进行一定操作即可.只是代码稍微有点长.
2.算法流程:
1.首先对于这颗树的每个点找到它的重儿子,树链剖分需要重构一下这颗树让重儿子的节点优先跑出,这里只需要一个dfs即可.
2.重构完树了之后呢?就只要把这颗树按节点序号投影到线段树即可.然后用线段树的操作来对于树进行操作.
3.对于模板题 一个很显然的结论,在树重构了之后,我对于子树u进行操作就是对于线段树区间(u,u+siz[u]-1)进行操作.然后对于树上路径是唯一的,我们可以采用倍增的思想对于u->v这条路径进行操作,这里我们需要记录一个东西,就是重链的起点是哪里,对于不是一颗重链的点,我们就可以一直跳.比较一下深度即可.
3.时间复杂度分析:
因为假如u->v为轻边,那么siz[v]<siz[u]/2的,否则为重边,如此可以判断轻边数量不超过logn的,然后线段树查询时logn的,所以时间复杂度就是的.
4.代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+500;
ll n,m,r,p;
ll w[N];
vector<int>v[N];
ll dep[N],siz[N],dfn[N],f[N];
//跑出深度,子树大小,重儿子,父亲.
void dfs1(int u,int fa)
{
siz[u]=1;dep[u]=dep[fa]+1;f[u]=fa;
int num=v[u].size();
for(int i=0;i<num;i++)
{
int son=v[u][i];
if(son==fa) continue;
dfs1(son,u);
siz[u]+=siz[son];
if(siz[son]>siz[dfn[u]]) dfn[u]=son;
}
}
//跑出每个节点的下标以及处理它们的权值,然后统计重链的起点.
ll id=0,idx[N],top[N],val[N];
void dfs2(int u,int topf)
{
idx[u]=++id;
top[u]=topf;
val[id]=w[u];
if(!dfn[u]) return;
dfs2(dfn[u],topf);
int num=v[u].size();
for(int i=0;i<num;i++)
{
int son=v[u][i];
if(idx[son]) continue;
dfs2(son,son);
}
}
//写一份有线段树区间修改,区间求和功能的树.
struct Tree{
ll l,r,len,sum,lazy;
}tr[N<<2];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
tr[u<<1].sum=(tr[u<<1].sum+tr[u].lazy*tr[u<<1].len%p)%p;
tr[u<<1|1].sum=(tr[u<<1|1].sum+tr[u].lazy*tr[u<<1|1].len%p)%p;
tr[u<<1].lazy=(tr[u<<1].lazy+tr[u].lazy)%p;
tr[u<<1|1].lazy=(tr[u<<1|1].lazy+tr[u].lazy)%p;
tr[u].lazy=0;
}
void build(int u,int l,int r)
{
tr[u].len=(r-l+1);tr[u].l=l;tr[u].r=r;
if(l==r)
{
tr[u].l=tr[u].r=l;
tr[u].sum=val[l];
return;
}int mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void add(int u,int L,int R,int k)
{
if(tr[u].r<L||R<tr[u].l) return;
if(L<=tr[u].l&&tr[u].r<=R)
{
tr[u].sum=(tr[u].sum+k*(tr[u].len)%p)%p;
tr[u].lazy=(tr[u].lazy+k)%p;
return;
}pushdown(u);
add(u<<1,L,R,k);
add(u<<1|1,L,R,k);
pushup(u);
}
ll query(int u,int L,int R)
{
if(tr[u].r<L||R<tr[u].l) return 0;
if(L<=tr[u].l&&tr[u].r<=R) return tr[u].sum%p;
pushdown(u);
return (query(u<<1,L,R)+query(u<<1|1,L,R))%p;
}
//实现v->u的修改以及查询操作.
void Treeadd(int x,int y,int k)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
add(1,idx[top[x]],idx[x],k);
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
add(1,idx[x],idx[y],k);
}
ll Treesum(int x,int y)
{
ll ans=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ans=(ans+query(1,idx[top[x]],idx[x]))%p;
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
ans=(ans+query(1,idx[x],idx[y]))%p;
return ans;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&r,&p);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(r,r);
dfs2(r,r);
build(1,1,n);
while(m--)
{
int op,x,y,z;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d%d",&x,&y,&z);
Treeadd(x,y,z%p);
}
else if(op==2)
{
scanf("%d%d",&x,&y);
printf("%lld\n",Treesum(x,y));
}
else if(op==3)
{
scanf("%d%d",&x,&z);
add(1,idx[x],idx[x]+siz[x]-1,z%p);
}
else
{
scanf("%d",&x);
printf("%lld\n",query(1,idx[x],idx[x]+siz[x]-1));
}
}
return 0;
}
京公网安备 11010502036488号