树上任意两点间路径可由$logn$条重链组成,所有重链视为连续的区间,用线段树维护。对路径的修改查询,只需修改$logn$个区间,总复杂度$O(log^2n)$

// p3384
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;

const int N = 100010;
int n, m, a, b, root, P, w[N];
#define ll long long
#define lc (u << 1)
#define rc (u << 1 | 1)
vector<int> e[N];
int fa[N], dep[N], sz[N], son[N];
int top[N], id[N], nw[N], cnt;  // 重链

/*
    fa[u] u的父节点
    dep[u] u的深度
    sz[u] 以u为根的子树节点数
    son[u] u的重儿子
    top[u] u所在重链的顶点
    id[u] u剖分后的新编号cnt
    nw[cnt] 新编号对应的权值
*/

struct tree {
    int l, r;
    ll add, sum;
} tr[N * 4];  // 线段树

void dfs1(int u, int father) {  // 搞fa,dep,sz,son
    fa[u] = father, dep[u] = dep[father] + 1, sz[u] = 1;
    for (int v : e[u]) {
        if (v == father)
            continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[son[u]] < sz[v])
            son[u] = v;
    }
}
void dfs2(int u, int t) {  // 搞top,id,nw
    top[u] = t, id[u] = ++cnt, nw[cnt] = w[u];
    if (!son[u])
        return;
    dfs2(son[u], t);
    for (int v : e[u]) {
        if (v == fa[u] || v == son[u])
            continue;
        dfs2(v, v);
    }
}
void pushup(int u) {
    tr[u].sum = tr[lc].sum + tr[rc].sum;
}
void pushdown(int u) {
    if (tr[u].add) {
        tr[lc].sum += tr[u].add * (tr[lc].r - tr[lc].l + 1);
        tr[rc].sum += tr[u].add * (tr[rc].r - tr[rc].l + 1);
        tr[lc].add += tr[u].add;
        tr[rc].add += tr[u].add;
        tr[u].add = 0;
    }
}
void build(int u, int l, int r) {  // 构建线段树
    tr[u] = {l, r, 0, nw[r]};
    if (l == r)
        return;
    int mid = l + r >> 1;
    build(lc, l, mid), build(rc, mid + 1, r);
    pushup(u);
}
ll query(int u, int l, int r) {  // 线段树查询
    if (l <= tr[u].l && tr[u].r <= r)
        return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    ll res = 0;
    if (l <= mid)
        res += query(lc, l, r);
    if (r > mid)
        res += query(rc, l, r);
    return res;
}
ll query_path(int u, int v) {  // 查询路径
    ll res = 0;
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        res += query(1, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v])
        swap(u, v);
    res += query(1, id[v], id[u]);  // 最后一段
    return res;
}
ll query_tree(int u) {  // 查询子树
    return query(1, id[u], id[u] + sz[u] - 1);
}
void update(int u, int l, int r, int k) {  // 线段树修改
    if (l <= tr[u].l && tr[u].r <= r) {
        tr[u].add += k;
        tr[u].sum += k * (tr[u].r - tr[u].l + 1);
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid)
        update(lc, l, r, k);
    if (r > mid)
        update(rc, l, r, k);
    pushup(u);
}
void update_path(int u, int v, int k) {  // 修改路径
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        update(1, id[top[u]], id[u], k);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v])
        swap(u, v);
    update(1, id[v], id[u], k);  // 最后一段
}
void update_tree(int u, int k) {  // 修改子树
    update(1, id[u], id[u] + sz[u] - 1, k);
}
int main() {
    scanf("%d%d%d%d", &n, &m, &root, &P);
    for (int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    for (int i = 0; i < n - 1; i++) {
        scanf("%d%d", &a, &b);
        e[a].push_back(b);
        e[b].push_back(a);
    }
    dfs1(root, 0);
    dfs2(root, root);  // 把树拆成链
    build(1, 1, n);    // 用链建线段树
    while (m--) {
        int t, u, v, k;
        scanf("%d%d", &t, &u);
        if (t == 1) {
            scanf("%d%d", &v, &k);
            update_path(u, v, k);
        } else if (t == 3) {
            scanf("%d", &k);
            update_tree(u, k);
        } else if (t == 2) {
            scanf("%d", &v);
            printf("%d\\n", query_path(u, v) % P);
        } else
            printf("%d\\n", query_tree(u) % P);
    }
}