RT
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lc id<<1
#define rc id<<1|1
const ll MAXN=100005;
ll n,m,r,mod,wws[MAXN],wt[MAXN];//题目变量
//------------------------------------------------------------
//链式前向星
struct edge{
ll next,to;
}e[MAXN<<1];
ll head[MAXN],cnt;
void add(ll a,ll b){
e[++cnt].to=b;
e[cnt].next=head[a];
head[a]=cnt;
}
//------------------------------------------------------------
//线段树
struct tree{
ll l,r,lazy,sum;
}tr[MAXN<<2];
void build(ll id,ll l,ll r){
tr[id].l=l,tr[id].r=r;
if(l==r){
tr[id].sum=wt[l];
return ;
}
ll mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
tr[id].sum=tr[lc].sum+tr[rc].sum;
return ;
}
void pushdown(ll id){
if(tr[id].lazy&&tr[id].l!=tr[id].r){
tr[lc].lazy+=tr[id].lazy;
tr[rc].lazy+=tr[id].lazy;
tr[lc].sum+=tr[id].lazy*(tr[lc].r-tr[lc].l+1);
tr[rc].sum+=tr[id].lazy*(tr[rc].r-tr[rc].l+1);
tr[id].lazy=0;
}
return ;
}
void add(ll id,ll l,ll r,ll val){//区间修改
if(l<=tr[id].l&&tr[id].r<=r){
tr[id].sum+=val*(tr[id].r-tr[id].l+1);
tr[id].lazy+=val;
return ;
}
pushdown(id);
//发现没有被覆盖,就需要继续向下找,考虑到儿子区间可能因为懒标记的存在而没有被修改,所以此处就需要下放懒标记
ll mid=(tr[id].l+tr[id].r)/2;
if(l<=mid) add(lc,l,r,val);
if(r>mid) add(rc,l,r,val);
tr[id].sum=tr[lc].sum+tr[rc].sum;
return ;
}
ll query(ll id,ll l,ll r){
if(l<=tr[id].l&&tr[id].r<=r) return tr[id].sum;
pushdown(id);
ll mid=(tr[id].l+tr[id].r)/2,val=0;
if(l<=mid) val+=query(lc,l,r);
if(r>mid) val+=query(rc,l,r);
return val;
}
//------------------------------------------------------------
//树剖核心
ll dep[MAXN],f[MAXN],son[MAXN],siz[MAXN],top[MAXN];
//dep:深度 f:父亲节点 son:重儿子 siz:子树大小 top:所在链的顶点
ll dfn[MAXN],num;
//dfn:dfs序 num:计数 wt:将初始值赋值到新编号上,以便进行线段树操作
void dfs1(ll x,ll fa){//获取 dep、fa、son、siz
f[x]=fa;
siz[x]=1,dep[x]=dep[f[x]]+1;
for(ll i=head[x];i;i=e[i].next){
ll y=e[i].to;
if(y!=f[x]){
dfs1(y,x);
siz[x]+=siz[y];
//迭代更新子树大小
if(!son[x]||siz[son[x]]<siz[y]) son[x]=y;
//没有重儿子,或当前重儿子字数大小不及 y节点,则更新
}
}
}
void dfs2(ll x,ll tv){//获取 top
dfn[x]=++num;//dfs序
wt[num]=wws[x];
top[x]=tv;//从端点出发
if(son[x]) dfs2(son[x],tv);//从重儿子继续连边
for(ll i=head[x];i;i=e[i].next){
ll y=e[i].to;
if(y==f[x]||y==son[x]) continue;//保证 y不为父节点或重儿子 (重儿子已处理过)
dfs2(y,y);//因重链端点定为轻儿子,故从 y更新 (新开链 )
}
return ;
}
ll query_range(ll x,ll y){//区间查询
ll ans=0;
while(top[x]!=top[y]){//如果两个点不在一条链上
if(dep[top[x]]<dep[top[y]]) swap(x,y);//把 x 改为所在链顶端深度更低那个点
ans=(ans+query(1,dfn[top[x]],dfn[x])%mod)%mod;//链上编号连续,区间和为从链顶端到 x 之和
// cout<<x<<" "<<y<<" "<<query(1,dfn[top[x]],dfn[x])<<"\n";
x=f[top[x]];//跳到链顶端的父节点,开始下一轮操作
}
if(dep[x]>dep[y]) swap(x,y);// x 与 y 此时在一条节点上,要确保 x 的编号在 y 的编号前,以便查询
ans=(ans+query(1,dfn[x],dfn[y])%mod)%mod;
// cout<<x<<" "<<y<<" "<<query(1,dfn[x],dfn[y])<<"\n";
return ans;
}
ll query_son(ll x){//子树查询
return query(1,dfn[x],dfn[x]+siz[x]-1);//整棵子树编号连续,和为从第一个节点(根节点)到最后一个节点的区间和
}
void add_range(ll x,ll y,ll val){//区间修改,原理同上
val%=mod;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
add(1,dfn[top[x]],dfn[x],val);
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
add(1,dfn[x],dfn[y],val);
return ;
}
void add_son(ll x,ll val){
add(1,dfn[x],dfn[x]+siz[x]-1,val);
return ;
}
//------------------------------------------------------------
signed main(){
// freopen("P3384_4.in","r",stdin);
// freopen("P33844.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m>>r>>mod;
for(ll i=1;i<=n;i++) cin>>wws[i];
for(ll i=1;i<n;i++){
ll x,y;
cin>>x>>y;
add(x,y);add(y,x);
}
dfs1(r,0);dfs2(r,r);//注意是以 r 为根节点
build(1,1,n);
// for(ll i=1;i<=n;i++) cout<<dfn[i]<<" ";
// cout<<"\n";
// for(ll i=1;i<=n;i++) cout<<siz[i]<<" ";
// cout<<"\n";
// for(ll i=1;i<=n;i++) cout<<f[i]<<" ";
// cout<<"\n";
// for(ll i=1;i<=n*2;i++){
// cout<<i<<":"<<tr[i].l<<" "<<tr[i].r<<" "<<tr[i].sum<<"\n";
// }
// cout<<"\n";
for(ll i=1;i<=m;i++){
ll cz,xx,yy,zz;
cin>>cz;
if(cz==1){
cin>>xx>>yy>>zz;
add_range(xx,yy,zz);
}
if(cz==2){
cin>>xx>>yy;
cout<<query_range(xx,yy)<<"\n";
}
if(cz==3){
cin>>xx>>zz;
add_son(xx,zz);
}
if(cz==4){
cin>>xx;
cout<<query_son(xx)<<"\n";
}
// for(ll i=1;i<=n*2-1;i++) cout<<i<<":"<<tr[i].l<<" "<<tr[i].r<<" "<<tr[i].sum<<"\n";
// cout<<"\n";
}
return 0;