//树链剖分
//将最短路上的区间分成若干条链 使得每一条链上的操作都能用线段树解决
//分轻重 重儿子为某节点的儿子中儿子最多的子节点 重儿子之间的边为重边 重边相连即为重链
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,root,MOD,dot[100001],dotid[100001];//dot[]儿子编号 dotid[]儿子新编号
int cnt,head[100001];
struct uio{
int to,next;
}edge[200001];//链式前向星
int seg[400001],lazy[400001];//线段树数组
int hvyson[100001],id[100001],fa[100001],dfsno,dep[100001],siz[100001],top[100001];
//hvyson[]重儿子编号 id[]新编号(新dfs序) fa[]父节点 dfsno为dfs序 dep[]深度 siz[]子树大小 top[]当前链顶端节点
void add(int x,int y)
{
edge[++cnt].next=head[x];
edge[cnt].to=y;
head[x]=cnt;
}
//--------------------------------------------------以下为线段树
void pushdown(int now,int len)
{
int lson=now<<1,rson=(now<<1)|1;
lazy[lson]+=lazy[now];
lazy[rson]+=lazy[now];
seg[lson]+=lazy[now]*(len-(len>>1));
seg[rson]+=lazy[now]*(len>>1);
seg[lson]%=MOD;
seg[rson]%=MOD;
lazy[now]=0;
}
void build(int now,int l,int r)
{
if(l==r)
{
seg[now]=dotid[l];
seg[now]%=MOD;
return;
}
int mid=(l+r)/2;
build(now<<1,l,mid);
build((now<<1)|1,mid+1,r);
seg[now]=(seg[now<<1]+seg[(now<<1)|1])%MOD;
}
int query(int now,int l,int r,int L,int R)
{
int ans=0;
if(L<=l&&r<=R)
{
ans+=seg[now];
return ans%MOD;
}
pushdown(now,r-l+1);
int mid=(l+r)/2;
if(L<=mid)
ans+=query(now<<1,l,mid,L,R);
if(mid+1<=R)
ans+=query((now<<1)|1,mid+1,r,L,R);
return ans%MOD;
}
void add_update(int now,int l,int r,int L,int R,int z)
{
if(L<=l&&r<=R)
{
lazy[now]+=z;
seg[now]+=z*(r-l+1);
return;
}
pushdown(now,r-l+1);
int mid=(l+r)/2;
if(L<=mid)
add_update(now<<1,l,mid,L,R,z);
if(mid+1<=R)
add_update((now<<1)|1,mid+1,r,L,R,z);
seg[now]=(seg[now<<1]+seg[(now<<1)|1])%MOD;
}
//--------------------------------------------------以上为线段树
void dfs1(int x,int f,int depth)//求普通dfs序 目的计算子树大小
{
dep[x]=depth;//标记深度
fa[x]=f;//标记父亲
siz[x]=1;//标记子树大小
int maxson=-1;//记录重儿子的儿子数
for(int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if(y==f)
continue;
dfs1(y,x,depth+1);
siz[x]+=siz[y];//把它的儿子的儿子数加到它身上
if(siz[y]>maxson)
{
hvyson[x]=y;//标记重儿子
maxson=siz[y];
}
}
}
void dfs2(int x,int topf)//topf为当前重链的最顶端节点
{
id[x]=++dfsno;//标记每个点的新编号(新dfs序)
dotid[dfsno]=dot[x];//把点值赋值到新编号上
top[x]=topf;//标记重链顶端
if(!hvyson[x])//没有重儿子就返回
return;
dfs2(hvyson[x],topf);//优先dfs重儿子
for(int i=head[x];i;i=edge[i].next)
{
int y=edge[i].to;
if(y==fa[x]||y==hvyson[x])
continue;
dfs2(y,y);//对于每个轻儿子其重链顶端都为自己
}
}
void update_section(int x,int y,int z)
{
z%=MOD;
while(top[x]!=top[y])//若两个点不在同一条重链上
{
if(dep[top[x]]<dep[top[y]])//将x点改为所在重链顶端深度更深的那个点
swap(x,y);
add_update(1,1,n,id[top[x]],id[x],z);//先修改从x到x所在重链顶端的点的权值
x=fa[top[x]];//把x跳到x所在重链顶端的那个点
}
//直至两点位于同一条重链上
if(dep[x]>dep[y])//把x改为深度更浅的那个点
swap(x,y);
add_update(1,1,n,id[x],id[y],z);
}
int query_section(int x,int y)//注释全部同上
{
int ans=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
ans+=query(1,1,n,id[top[x]],id[x]);
ans%=MOD;
x=fa[top[x]];
}
if(dep[x]>dep[y])
swap(x,y);
ans+=query(1,1,n,id[x],id[y]);
return ans%MOD;
}
void update_son(int x,int z)//直接用线段树处理
{
add_update(1,1,n,id[x],id[x]+siz[x]-1,z);
}
int query_son(int x)//同上
{
return query(1,1,n,id[x],id[x]+siz[x]-1);
}
void do_something()
{
for(int i=1;i<=m;i++)
{
int k,u,v,w;
scanf("%d",&k);
if(k==1)
{
scanf("%d%d%d",&u,&v,&w);
update_section(u,v,w);
}
if(k==2)
{
scanf("%d%d",&u,&v);
printf("%d\n",query_section(u,v));
}
if(k==3)
{
scanf("%d%d",&u,&v);
update_son(u,v);
}
if(k==4)
{
scanf("%d",&u);
printf("%d\n",query_son(u));
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&root,&MOD);
for(int i=1;i<=n;i++)
scanf("%d",&dot[i]);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs1(root,0,1);
dfs2(root,root);
build(1,1,n);
do_something();
return 0;
}