/*
MJJ's 树链剖分--模板
LCA + 线段树
2022.08.08
*/
#include<bits/stdc++.h>
using namespace std;
#define maxn 5000005
#define ll long long
int n; //树的节点总数
int m; //m次询问
int root; //根节点
int mod; //取模
struct node
{
int to;
int next;
}edge[maxn<<1];
int cnt,head[maxn]; //链式前向星存树
inline void add(int u,int v)
{
edge[++cnt].next = head[u];
edge[cnt].to = v;
head[u] = cnt;
}
int dep[maxn]; //x节点的深度
int fa[maxn]; //x节点的父亲节点
int son[maxn]; //x节点的重儿子
int siz[maxn]; //x节点为根的子树大小
int top[maxn]; //x节点所在链的顶点
inline void DFS1(int now,int fath)
{
/*传入当前节点和其父亲节点的编号*/
fa[now] = fath;
siz[now] = 1;son[now] = 0;
dep[now] = dep[fath] + 1;
for(int i=head[now];i;i=edge[i].next)
{
if(edge[i].to==fath)
continue;
DFS1(edge[i].to,now);
siz[now] += siz[edge[i].to];
if(siz[son[now]]<siz[edge[i].to]) //当前now的重儿子不是最大时更新
son[now] = edge[i].to;
}
}
//son[maxn] x节点的重儿子
inline void DFS2(int now,int topx)
{
/*先重儿子再轻儿子*/
top[now] = topx; //重链的顶端
if(son[now])
DFS2(son[now],topx); //找重儿子
else
return;
for(int i=head[now];i;i=edge[i].next)
{
if(edge[i].to!=fa[now]&&edge[i].to!=son[now])
DFS2(edge[i].to,edge[i].to); //找轻儿子,构造轻链
}
}
//---------------------------分割线--------------------------------//
/*----------树剖LCA----------*/
inline int LCA(int x,int y)
{
/*查询节点x与节点y的最近公共祖先*/
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
x = fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
//---------------------------分割线--------------------------------//
/*----------树剖线段树----------*/
int nid[maxn]; //新的节点编号
int nw[maxn]; //新的节点对应的权重
int w[maxn]; //初始权值
int pos;
inline void DFS2_(int now,int topx)
{
/*先重儿子再轻儿子*/
top[now] = topx; //重链的顶端
/*新增两行代码*/
nid[now] = ++pos; //新的节点编号
nw[pos] = w[now]; //新的权重
if(son[now])
DFS2_(son[now],topx); //找重儿子
else
return;
for(int i=head[now];i;i=edge[i].next)
{
if(edge[i].to!=fa[now]&&edge[i].to!=son[now])
DFS2_(edge[i].to,edge[i].to); //找轻儿子,构造轻链
}
}
//线段树数组与懒惰标记数组
int tree[maxn<<2],tag[maxn<<2];
inline void up(int p)
{
int ls = p<<1; //左孩子
int rs = p<<1|1; //右孩子
tree[p] = tree[ls] + tree[rs];
}
inline void build(int l,int r,int p) //建立线段树
{
int ls = p<<1; //左孩子
int rs = p<<1|1; //右孩子
int mid = (l+r)>>1;
if(l==r)
{
tree[p] = nw[l];
return;
}
build(l,mid,ls);
build(mid+1,r,rs);
up(p);
}
inline void down(int l,int r,int p) //下传标记
{
int ls = p<<1; //左孩子
int rs = p<<1|1; //右孩子
int mid = (l+r)>>1;
tag[ls] += tag[p];
tag[rs] += tag[p];
tree[ls] += (mid-l+1)*tag[p];
tree[ls] %= mod;
tree[rs] += (r-mid)*tag[p];
tree[rs] %= mod;
tag[p] = 0;
}
inline void update(int l,int r,int nl,int nr,int p,int k) //区间修改
{
/*
l,r:线段树的范围
nl,nr:当前修改的区间范围
p:当前访问的子树的根节点
k:修改的元素值
*/
int ls = p<<1; //左孩子
int rs = p<<1|1; //右孩子
int mid = (l+r)>>1;
if(l>=nl&&r<=nr)
{
tree[p] += (r-l+1)*k;
tree[p] %= mod;
tag[p] += k;
return;
}
down(l,r,p);
if(mid>=nl) update(l,mid,nl,nr,ls,k);
if(mid<nr) update(mid+1,r,nl,nr,rs,k);
up(p);
}
inline ll query(int l,int r,int nl,int nr,int p)
{
if(l>=nl&&r<=nr)
// return tree[p];
return tree[p]%mod;
ll res = 0;
int ls = p<<1; //左孩子
int rs = p<<1|1; //右孩子
int mid = (l+r)>>1;
down(l,r,p);
if(mid>=nl) res += query(l,mid,nl,nr,ls);
if(mid<nr) res += query(mid+1,r,nl,nr,rs);
return res;
}
// 利用nid数组的翻译来调用线段树
/*
-------模板-------
void func(int x,int y,int k)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
你的操作
x = fa[top[x]];
}
你的操作
}
*/
inline ll q_path(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,n,nid[top[x]],nid[x],1);
ans = (ans+query(1,n,nid[top[x]],nid[x],1))%mod;
x = fa[top[x]];
}
if(dep[x]>dep[y])
swap(x,y);
// ans = ans+query(1,n,nid[x],nid[y],1);
ans = (ans+query(1,n,nid[x],nid[y],1))%mod;
// return ans;
return ans%mod;
}
inline ll q_tree(int x) //访问整棵线段树
{
// return query(1,n,nid[x],nid[x]+siz[x]-1,1);
return query(1,n,nid[x],nid[x]+siz[x]-1,1)%mod;
}
inline void up_path(int x,int y,int k) //更新线段树的某条路径
{
// k %= mod;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
update(1,n,nid[top[x]],nid[x],1,k);
x = fa[top[x]];
}
if(dep[x]>dep[y])
swap(x,y);
update(1,n,nid[x],nid[y],1,k);
}
inline void up_tree(int x,int k) //更新整颗线段树
{
update(1,n,nid[x],nid[x]+siz[x]-1,1,k);
}
int main()
{
// 洛谷p3379
// int n,m,s;
// cin>>n>>m>>s;
// for(int i=1;i<n;++i)
// {
// int u,v;
// cin>>u>>v;
// add(u,v);
// add(v,u);
// }
// DFS1(s,0); //s为根节点
// DFS2(s,s);
// for(int i=1;i<=m;++i)
// {
// int x,y;
// cin>>x>>y;
// cout<<LCA(x,y)<<endl;
// }
//洛谷p3384
cin>>n>>m>>root>>mod;
for(int i=1;i<=n;++i) cin>>w[i];
for(int i=1;i<n;++i)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
DFS1(root,0);
DFS2_(root,root);
build(1,n,1);
for(int i=1;i<=m;++i)
{
int c,x,y,k;
cin>>c;
if(c==1)
{
cin>>x>>y>>k;
up_path(x,y,k);
}
else if(c==2)
{
cin>>x>>y;
cout<<q_path(x,y)<<endl;
}
else if(c==3)
{
cin>>x>>k;
up_tree(x,k);
}
else
{
cin>>x;
cout<<q_tree(x)<<endl;
}
}
return 0;
}
树链剖分 [LCA+线段树 模板]
最新推荐文章于 2024-08-30 15:57:36 发布