rt
#include<cstdio>
#include<iostream>
#define int long long
using namespace std;
const int maxn=100001;
int n,m,r,p;
int head[maxn],ver[maxn*2],nex[maxn*2],tot;
void add(int x,int y)
{
ver[++tot]=y;
nex[tot]=head[x],head[x]=tot;
}
int son[maxn],fa[maxn],size[maxn],deep[maxn];
void dfs_getson(int x)
{
size[x]=1;//初始化子树x的大小
for(int i=head[x];i;i=nex[i])//遍历x点连接的每一条边
{
int y=ver[i];
if(y==fa[x]) continue;//y是x父结点,跳过
fa[y]=x;//y点父结点为x
deep[y]=deep[x]+1;//y点所在深度为x点+1
dfs_getson(y);//获取子树y的信息
size[x]+=size[y];//子树x的大小要加上子树y的大小
if(size[y]>size[son[x]]) son[x]=y;//更新x点的重儿子
}
}
int dfn_past[maxn],dfn_new[maxn],top[maxn],cnt,ctr[maxn];
void dfs_rewrite(int x,int tp)//tp是x所在重链深度最小的那个点(顶点)
{
top[x]=tp;
dfn_new[x]=++tot;//得到每个点的新dfn
dfn_past[tot]=x;//记录新dfn所对应的原来的点
if(son[x]) dfs_rewrite(son[x],tp);//优先遍历x点的重儿子
for(int i=head[x];i;i=nex[i])
{
int y=ver[i];
if(y!=fa[x]&&y!=son[x]) dfs_rewrite(y,y);//y点是轻儿子,成为新重链的顶点
}
ctr[x]=cnt;//在回溯的时候标记这个子树的最大编号(新)
}
int point[maxn];//存点权
//线段树part
struct Node{
int l,r,add,val;
}t[maxn*4];
void build(int p,int l,int r,int a[])//在a数组(形参)上建线段树
{
t[p].l=l,t[p].r=r;
if(l==r)
{
t[p].val=a[l]%p;
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid,a);
build(p<<1|1,mid+1,r,a);
t[p].val=(t[p<<1].val+t[p<<1|1].val)%p;
}
void spread(int p)
{
if(t[p].add)
{
t[p<<1].add=(t[p<<1].add+t[p].add)%p;
t[p<<1|1].add=(t[p<<1|1].add+t[p].add)%p;
t[p<<1].val=(t[p<<1].val+t[p].add*(t[p<<1].r-t[p<<1].l+1))%p;
t[p<<1|1].val=(t[p<<1|1].val+t[p].add*(t[p<<1|1].r-t[p<<1].l+1))%p;
t[p].add=0;
}
}
void update(int p,int l,int r,int k)
{
if(l<=t[p].l&&r>=t[p].r)
{
t[p].add=(t[p].add+k)%p;
t[p].val=(t[p].val+(t[p].r-t[p].l+1)*k)%p;
return;
}
spread(p);
int mid=(t[p].r+t[p].l)>>1;
if(l<=mid) update(p<<1,l,r,k);
if(r>mid) update(p<<1|1,l,r,k);
t[p].val=(t[p<<1].val+t[p<<1|1].val)%p;
}
int query_sum(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r)
return t[p].val%p;
spread(p);
int mid=(t[p].r+t[p].l)>>1,val=0;
if(l<=mid) val=(val+query_sum(p<<1,l,r))%p;
if(r>mid) val=(val+query_sum(p<<1|1,l,r))%p;
return val%p;
}
int query_max(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r)
return t[p].val%p;
spread(p);
int mid=(t[p].r+t[p].l)>>1,val=0;
if(l<=mid) val=max(query_max(p<<1,l,r),val)%p;
if(r>mid) val=max(query_max(p<<1|1,l,r),val)%p;
return val%p;
}
//下面是树上操作
void tree_update()//树上修改
{
int u,v,k;//u到v路径上所有点权增加k
scanf("%lld%lld%lld",&u,&v,&k);
while(top[u]!=top[v])//当两点不在同一条重链上
{
if(deep[top[u]]>deep[top[v]]) swap(u,v);//优先跳重链顶点深的,存在v里面
update(1,dfn_new[top[v]],dfn_new[v],k);//v到它所在重链顶点的新编号是连续的~
v=fa[top[v]];//v点往上跳
}
if(deep[u]>deep[v]) swap(u,v);//在同一条重链上的点,深度小的编号也小
update(1,dfn_new[u],dfn_new[v],k);//同上
}
void tree_query_sum()//树上路径点权和查询,和上面差不多
{
int u,v,ans;
scanf("%lld%lld",&u,&v);
while(top[u]!=top[v])
{
if(deep[top[u]]>deep[top[v]]) swap(u,v);
ans+=query_sum(1,dfn_new[top[v]],dfn_new[v]);
v=fa[top[v]];
}
if(deep[u]>deep[v]) swap(u,v);
ans+=query_sum(1,dfn_new[u],dfn_new[v]);
printf("%lld\n",ans);
}
void tree_query_max()//树上路径最大点权,和上面差不多
{
int u,v,ans;
scanf("%lld%lld",&u,&v);
while(top[u]!=top[v])
{
if(deep[top[u]]>deep[top[v]]) swap(u,v);
ans=max(ans,query_max(1,dfn_new[top[v]],dfn_new[v]));
v=fa[top[v]];
}
if(deep[u]>deep[v]) swap(u,v);
ans=max(ans,query_max(1,dfn_new[u],dfn_new[v]));
printf("%lld\n",ans);
}
//下面是关于子树的操作
void sontree_update()
{
int x,k;
scanf("%lld%lld",&x,&k);//子树x内所有点点权+k
update(1,dfn_new[x],ctr[x],k);//因为编号连续就可以直接update
}
void sontree_query_sum()
{
int x;
scanf("%lld%lld",&x);//查询子树x点权和
printf("%lld\n",query_sum(1,dfn_new[x],ctr[x]));
}
void sontree_query_max()
{
int x;
scanf("%lld%lld",&x);//查询子树x中最大点权
printf("%lld\n",query_max(1,dfn_new[x],ctr[x]));
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&r,&p);
int opt,x,y;
for(int i=1;i<=n;i++)
scanf("%lld",&point[i]);
for(int i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
add(x,y);
add(y,x);
}
dfs_getson(r);
dfs_rewrite(r,r);
build(1,1,n,point);
for(int i=1;i<=m;i++)
{
scanf("%lld",&opt);
if(opt==1) tree_update();
if(opt==2) tree_query_sum();
if(opt==3) sontree_update();
if(opt==4) sontree_query_sum();
}
return 0;
}