标签: 数据结构

Link-Cut Tree(动态树)原理与实现

Link-Cut Tree(动态树)原理与实现

概述

LCT是一种实用数据结构(?),它滋磁维护树上信息,而且这个树是可以变换结构的。具体而言,它维护了一个Splay森林,利用Splay的功能可以维护树上信息,且可以对树结构进行拆分、连接等操作。下面介绍LCT的原理与实现。

原理与实现

一些定义

辅助树(auxiliary tree):在LCT中,用于维护重链信息的Splay。其维护的有序性以深度为参数,即其中序遍历为该链从根到重儿子的顺序。
访问(access):从树根出发,将根到某个节点的路径设置为重链。
重儿子(偏好儿子,preferred child):访问到的树链的最底层节点。
重边(偏好边,preferred edge):重链中,指向重儿子方向的边。
重链(偏好链,preferred path):访问时走过的路径,由重边和重儿子组成。
所有的访问都会创造出重链,但是由于LCT的操作,重链可能会断掉。具体的操作需要等到讲到操作的时候才能够说清楚。
注意,辅助树是为了便于维护树上信息,其形态不一定与原树相同,我们后面会讲到辅助树与原树的关系。

树的结构

刚刚也说到了,实际上LCT维护的是一个Splay森林。一棵Splay维护的是一条重链的信息。下面是一个LCT的示意图。
180317a 1 LCT intro - Link-Cut Tree(动态树)原理与实现
图片来自Wikipedia:Link/cut tree – Wikipedia
图片中,粗边即为重边,而细边我们将它定义为轻边。左→中表示的是以a为根的有根树访问l节点所造成的变化。右图展示的是Splay的划分。
我们说,Splay维护的是重链信息,而当前Splay和原树中重链顶的父节点通过轻边(右图虚线箭头)来连接。
从现在开始,文章内容默认你已经会使用Splay的操作。如果Splay仍不会,可以参考Splay原理与实现 | KSkun’s Blog一文。

LCT的核心:访问(access)

访问,即从树根出发,将树根到某节点x的路径设置为重链的操作。要实现这个操作,我们需要将这一排点加入到一个Splay中。
实现上,我们要断掉链上的点与之前重儿子的连接,将其合并到一个Splay中。我们从重儿子x向树根处理,每次将节点splay到所在辅助树的根,此时该点的右子树为以前重链上需要切掉的一段。切断这条重边,并将右儿子的连边设置为向当前重儿子的重边即可。
实现上,我们统一用fa这一变量来存储父亲的信息,也就是说这个fa可能指的并非所在Splay的父节点,而是轻边的父节点。但是每个节点的儿子信息则完全是Splay中的儿子信息,即轻边的儿子不会在父节点有记录。因此我们只需更改每个节点的右儿子即可,并不需要重置右儿子的父亲信息。

inline void access(int p) {
    for(int q = 0; p; q = p, p = tr[p].fa) {
        splay(p);
        tr[p].ch[1] = q;
        update(p);
    }
}

换根(make_root)

换根,即将原树转换为以某节点x为树根的有根树。
我们可以访问该节点,并将其splay至根。由于Splay内部的有序以深度为依据,x只有左儿子,此时表示x在重链的底端。我们对这棵Splay进行翻转,x就变为重链的顶端,即原树树根。

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

找根(find_root)

找根,即找到某节点x所在原树的树根。
先访问该节点,splay至根。此时这条链上深度最小的点即为原树树根。因此我们只需找到最左的左儿子即可。

inline int findrt(int p) {
    access(p);
    splay(p);
    while(tr[p].ch[0]) p = tr[p].ch[0];
    return p;
}

分离树链(split)

分离树链,即将原树中某节点u到v的路径分离出来。
我们考虑将链的一端u设为树根,并访问v,将v splay到根。此时对于这条树链,在u、v所在的Splay上的体现即为v及v的左子树。

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

连接原树(link)

连接原树,即将某节点u、v所在的原树通过边(u, v)连接起来。
先将其中一点u设为原树根,以轻边接到v上即可。如果此处利用split,效果其实是一样的。

inline void link(int u, int v) {
    split(u, v);
    tr[u].fa = v;
}

切断原树(cut)

切断原树,即将本相连的节点u、v之间的边切断。
先将这条边split出来,然后切断父子关系即可。如果左儿子不是u或者左儿子有右儿子,那么表示不存在(u, v)这条边。

inline void cut(int u, int v) {
    split(u, v);
    if(tr[v].ch[0] != u || tr[tr[v].ch[0]].ch[1]) return;
    tr[u].fa = tr[v].ch[0] = 0;
}

改变点值(modify)

你需要把该点提至整棵树的树根,再做修改,这样可以避免修改后还要向上更新根的信息。

inline void modify(int u, int w) {
    access(u);
    splay(u);
    tr[u].val = w; // 更改值
    update(u);
}

与单独Splay操作的区别

由于我们用fa混着存两种边关系,即轻儿子、重儿子,我们不好判断Splay根的位置。下面是一种判断是否为Splay根的方法。

inline bool isroot(int p) {
    int fa = tr[p].fa;
    return tr[fa].ch[0] != p && tr[fa].ch[1] != p;
}

对于Splay中常见的旋转(rotate)、伸展(splay)操作,实现上有些许区别。主要原因判断Splay根的方式变了。

inline void rotate(int p) {
    bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
    tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
    tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
    tr[p].ch[!t] = fa; tr[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p); // 对p只update一次,优化常数
}

辅助树与原树的关系

原树在LCT中其实是剖成几条树链的,每条树链一个Splay。原树的结构中树链顺序,则为Splay的顺序,父子关系也依赖着轻重边来进行管理。但是LCT这个奇妙的Splay森林结构并不和原来的有根树完全相同,Splay根和树根也并不相同。
最开始看LCT就是在这里搞不懂的。而且直到现在我也不知道应该怎么写这段内容GG。

例题:【P3690】【模板】Link Cut Tree (动态树) – 洛谷

题目描述

  1. 求链点权异或和
  2. 连接树边
  3. 断开树边
  4. 改变点权

题解

我们在节点处维护一个子树异或和(Splay意义上的子树),每次查询split以后查。
修改操作则先访问它并且把这个点splay到根再改。

代码

// Code by KSkun, 2018/4
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 300005;

struct Node {
    int ch[2], fa, val, xsum;
    bool rev;
} tr[MAXN];

inline bool isleft(int p) {
    return tr[tr[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    return tr[tr[p].fa].ch[0] != p && tr[tr[p].fa].ch[1] != p;
}

inline void update(int p) {
    tr[p].xsum = tr[p].val ^ tr[tr[p].ch[0]].xsum ^ tr[tr[p].ch[1]].xsum;
}

inline void reverse(int p) {
    std::swap(tr[p].ch[0], tr[p].ch[1]);
    tr[p].rev ^= 1;
}

inline void pushdown(int p) {
    if(tr[p].rev) {
        if(tr[p].ch[0]) reverse(tr[p].ch[0]);
        if(tr[p].ch[1]) reverse(tr[p].ch[1]);
        tr[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = tr[p].fa;
    }
    sta[stop++] = p;
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
    tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
    tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
    tr[p].ch[!t] = fa; tr[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(int q = 0; p; q = p, p = tr[p].fa) {
        splay(p);
        tr[p].ch[1] = q;
        update(p);
    }
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(tr[p].ch[0]) p = tr[p].ch[0];
    return p;
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    tr[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(tr[v].ch[0] != u || tr[u].ch[1]) return;
    tr[u].fa = tr[v].ch[0] = 0;
}

inline void modify(int u, int w) {
    access(u);
    splay(u);
    tr[u].val = w;
    update(u);
}

int n, m, op, x, y;

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        tr[i].val = tr[i].xsum = readint();
    }
    while(m--) {
        op = readint(); x = readint(); y = readint();
        switch(op) {
        case 0:
            split(x, y);
            printf("%d\n", tr[y].xsum);
            break;
        case 1:
            link(x, y);
            break;
        case 2:
            cut(x, y);
            break;
        case 3:
            modify(x, y);
        }
    }
    return 0;
}

参考资料

[ZJOI2011]道馆之战 题解

[ZJOI2011]道馆之战 题解

题目地址:洛谷:【P4679】[ZJOI2011]道馆之战 – 洛谷、BZOJ:Problem 2325. — [ZJOI2011]道馆之战

题目描述

馆主为了加大难度,将道馆分成了n个房间,每个房间中是两个冰块或障碍,表示一列冰地。任意两个房间之间均有且仅有一条路径相连,即这n个房间构成一个树状结构。每个房间分成了A和B两个区域,每一区域都是一个薄冰块或者障碍物。每次只能移动到相邻房间的同一类区域(即若你现在在这个房间的A区域,那么你只能移动到相邻房间的A区域)或这个房间的另一区域。现在挑战者从房间u出发,馆主在房间v,那么挑战者只能朝接近馆主所在房间的方向过去。一开始挑战者可以在房间u的任意一个冰块区域内。如果挑战者踩过的冰块数达到了最大值(即没有一种方案踩过的冰块数更多了),那么当挑战者走到最后一个冰块上时,他会被瞬间传送到馆主面前与馆主进行道馆战。自从馆主修改规则后已经经过了m天,每天要么是有一个挑战者来进行挑战,要么就是馆主将某个房间进行了修改。对于每个来的挑战者,你需要计算出他若要和馆主进行战斗需要经过的冰块数。

输入输出格式

输入格式:
第一行包含两个正整数n和m。第2行到第n行,每行包含两个正整数x和y,表示一条连接房间x和房间y的边。房间编
号为1…n。接下来n行,每行包含两个字符。第n + k行表示房间k的两个区域,第一个字符为A区域,第二个字符为
B区域。其中“.”(ASCII码为46)表示是薄冰块,“#”(ASCII码为35)表示是障碍物。最后的m行,每行一个操作:
C u s:将房间u里的两个区域修改为s。
Q u v:询问挑战者在房间u,馆主在房间v时,挑战者能与馆主进行挑战需要踩的冰块数。如果房间u的两个区域
都是障碍物,那么输出0。
N≤ 30 000
M ≤ 80 000

输出格式:
包含若干行,每行一个整数。即对于输入中的每个询问,依次输出一个答案。

输入输出样例

输入样例#1:

5 3
1 2
2 3
2 4
1 5
.#
..
#.
.#
..
Q 5 3
C 1 ##
Q 4 5

输出样例#1:

6
3

题解

链上信息用树剖总是没错的,套个线段树。接下来的内容只研究区间信息和合并。
首先我们要求一个点出发能走的最远距离,自然要把这个答案记起来,那么用far[2][2]表示区间左1左2右1右2开始走最远距离(这里其实有点像[SHOI2008]堵塞的交通 题解 | KSkun’s Blog)。为了区间合并,我们还要记下dis[2][2]表示区间左边两个区域到右边两个区域分别的最远路径长度。
初始的时候,如果不能走,可以以-INF为初值,这样后面取max比较好办。以长度为1的区间开始设置初始值,设置的部分比较容易理解可以看代码或者类比SHOI毒瘤题。合并的时候,dis数组计算应当枚举跨越mid时是从1区域还是2区域通行的,far数组应当对跨越mid和不跨越mid分别统计。这个图示与SHOI毒瘤题其实长得差不多,可以参考那个题,结合代码理解。
查询的时候,可能要翻转其中的一半答案再合并成最后的答案。swap的时候可以考虑记下来起点在当前的哪一端。
题解参考了【BZOJ-2325】道馆之战 树链剖分 + 线段树 – DaD3zZ – 博客园,感谢原作者。

代码

// Code by KSkun, 2018/3
#include <cstdio>

#include <vector>
#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isstatus(char c) {
    return c == '.' || c == '#';
}

inline bool readstatus() {
    char c;
    while(!isstatus(c = fgc()));
    return c == '.';
}

inline bool isop(char c) {
    return c == 'Q' || c == 'C';
}

inline char readop() {
    char c;
    while(!isop(c = fgc()));
    return c;
}

const int MAXN = 300005, INF = 1e9;

int n, m, ut, vt;
char op;
bool w[MAXN][2];
int fa[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], ptn[MAXN], top[MAXN], dep[MAXN], cnt;
std::vector<int> gra[MAXN];

inline void addedge(int u, int v) {
    gra[u].push_back(v);
    gra[v].push_back(u);
}

inline void dfs1(int u) {
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa[u]) continue;
        dep[v] = dep[u] + 1;
        fa[v] = u;
        dfs1(v);
        siz[u] += siz[v] + 1;
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

inline void dfs2(int u, int tp) {
    top[u] = tp;
    dfn[u] = ++cnt;
    ptn[dfn[u]] = u;
    if(son[u]) dfs2(son[u], tp);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == son[u] || v == fa[u]) continue;
        dfs2(v, v);
    }
}

struct Node {
    int dis[2][2], far[2][2];
} sgt[MAXN << 2];

inline void merge(Node &rt, Node ls, Node rs) {
    rt.dis[0][0] = std::max(std::max(ls.dis[0][0] + rs.dis[0][0], ls.dis[0][1] + rs.dis[1][0]), -INF);
    rt.dis[0][1] = std::max(std::max(ls.dis[0][0] + rs.dis[0][1], ls.dis[0][1] + rs.dis[1][1]), -INF);
    rt.dis[1][0] = std::max(std::max(ls.dis[1][0] + rs.dis[0][0], ls.dis[1][1] + rs.dis[1][0]), -INF);
    rt.dis[1][1] = std::max(std::max(ls.dis[1][0] + rs.dis[0][1], ls.dis[1][1] + rs.dis[1][1]), -INF);
    rt.far[0][0] = std::max(ls.far[0][0], std::max(ls.dis[0][0] + rs.far[0][0], ls.dis[0][1] + rs.far[0][1]));
    rt.far[0][1] = std::max(ls.far[0][1], std::max(ls.dis[1][0] + rs.far[0][0], ls.dis[1][1] + rs.far[0][1]));
    rt.far[1][0] = std::max(rs.far[1][0], std::max(rs.dis[0][0] + ls.far[1][0], rs.dis[1][0] + ls.far[1][1]));
    rt.far[1][1] = std::max(rs.far[1][1], std::max(rs.dis[0][1] + ls.far[1][0], rs.dis[1][1] + ls.far[1][1]));
}

inline void reverse(Node &x) {
    std::swap(x.dis[0][1], x.dis[1][0]);
    std::swap(x.far[0][0], x.far[1][0]);
    std::swap(x.far[0][1], x.far[1][1]);
}

inline void build(int o, int l, int r) {
    if(l == r) {
        sgt[o].dis[0][0] = w[ptn[l]][0] ? 1 : -INF;
        sgt[o].far[0][0] = sgt[o].far[1][0] = w[ptn[l]][0];
        sgt[o].dis[1][1] = w[ptn[l]][1] ? 1 : -INF;
        sgt[o].far[0][1] = sgt[o].far[1][1] = w[ptn[l]][1];
        if(w[ptn[l]][0] && w[ptn[l]][1]) {
            sgt[o].far[0][0] = sgt[o].far[0][1] = sgt[o].far[1][0] = sgt[o].far[1][1] = 
                sgt[o].dis[0][1] = sgt[o].dis[1][0] = 2;
        } else {
            sgt[o].dis[0][1] = sgt[o].dis[1][0] = -INF;
        }
        return;
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    build(lch, l, mid);
    build(rch, mid + 1, r);
    merge(sgt[o], sgt[lch], sgt[rch]);
}

inline void modify(int o, int l, int r, int x) {
    if(l == r) {
        sgt[o].dis[0][0] = w[ptn[l]][0] ? 1 : -INF;
        sgt[o].far[0][0] = sgt[o].far[1][0] = w[ptn[l]][0];
        sgt[o].dis[1][1] = w[ptn[l]][1] ? 1 : -INF;
        sgt[o].far[0][1] = sgt[o].far[1][1] = w[ptn[l]][1];
        if(w[ptn[l]][0] && w[ptn[l]][1]) {
            sgt[o].far[0][0] = sgt[o].far[0][1] = sgt[o].far[1][0] = sgt[o].far[1][1] = 
                sgt[o].dis[0][1] = sgt[o].dis[1][0] = 2;
        } else {
            sgt[o].dis[0][1] = sgt[o].dis[1][0] = -INF;
        }
        return;
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(x <= mid) modify(lch, l, mid, x);
    else modify(rch, mid + 1, r, x);
    merge(sgt[o], sgt[lch], sgt[rch]);
}

inline Node query(int o, int l, int r, int ll, int rr) {
    if(l == ll && r == rr) {
        return sgt[o];
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(rr <= mid) {
        return query(lch, l, mid, ll, rr);
    } else if(ll > mid) {
        return query(rch, mid + 1, r, ll, rr);
    } else {
        Node ls = query(lch, l, mid, ll, mid), rs = query(rch, mid + 1, r, mid + 1, rr), res;
        merge(res, ls, rs);
        return res;
    }
}

inline int query(int u, int v) {
    Node su, sv, t, t1;
    bool fu = false, fv = false, iu = true;
    int tu = top[u], tv = top[v];
    while(tu != tv) {
        if(dep[tu] > dep[tv]) {
            std::swap(tu, tv);
            std::swap(u, v);
            std::swap(su, sv);
            std::swap(fu, fv);
            iu ^= 1;
        }
        t = query(1, 1, n, dfn[tv], dfn[v]);
        if(fv) merge(t1, t, sv), sv = t1; else sv = t, fv = true;
        v = fa[tv];
        tv = top[v];
    }
    if(dep[u] > dep[v]) {
        std::swap(u, v);
        std::swap(su, sv);
        std::swap(fu, fv);
        iu ^= 1;
    }
    t = query(1, 1, n, dfn[u], dfn[v]);
    if(fv) merge(t1, t, sv), sv = t1; else sv = t, fv = true;
    if(fu) reverse(su), merge(t1, su, sv); else t1 = sv;
    return std::max(t1.far[!iu][0], t1.far[!iu][1]);
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedge(ut, vt);
    }
    for(int i = 1; i <= n; i++) {
        w[i][0] = readstatus();
        w[i][1] = readstatus();
    }
    dfs1(1);
    dfs2(1, 1);
    build(1, 1, n);
    while(m--) {
        op = readop();
        if(op == 'Q') {
            ut = readint(); vt = readint();
            printf("%d\n", query(ut, vt));
        }
        if(op == 'C') {
            ut = readint(); w[ut][0] = readstatus(); w[ut][1] = readstatus();
            modify(1, 1, n, dfn[ut]);
        }
    }
    return 0;
}
[HAOI2015]树上操作 题解

[HAOI2015]树上操作 题解

题目地址:洛谷:【P3178】[HAOI2015]树上操作 – 洛谷、BZOJ:Problem 4034. — [HAOI2015]树上操作

题目描述

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

输入输出格式

输入格式:
第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

输出格式:
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

输入输出样例

输入样例#1:

5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3

输出样例#1:

6
9
13

说明

对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不会超过 10^6 。

题解

树剖那篇文章的例题还裸。

代码

 // Code by KSkun, 2018/3
#include <cstdio>

#include <vector>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

int n, m, w[MAXN], ut, vt, op, xt, at;
int fa[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], ptn[MAXN], top[MAXN], dep[MAXN], cnt;
std::vector<int> gra[MAXN];

inline void addedge(int u, int v) {
    gra[u].push_back(v);
    gra[v].push_back(u);
}

inline void dfs1(int u) {
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == fa[u]) continue;
        dep[v] = dep[u] + 1;
        fa[v] = u;
        dfs1(v);
        siz[u] += siz[v] + 1;
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

inline void dfs2(int u, int tp) {
    top[u] = tp;
    dfn[u] = ++cnt;
    ptn[dfn[u]] = u;
    if(son[u]) dfs2(son[u], tp);
    for(int i = 0; i < gra[u].size(); i++) {
        int v = gra[u][i];
        if(v == son[u] || v == fa[u]) continue;
        dfs2(v, v);
    }
}

struct Node {
    LL val, add;
} sgt[MAXN << 2];

inline void pushdown(int o, int l, int r) {
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(sgt[o].add) {
        sgt[lch].add += sgt[o].add;
        sgt[rch].add += sgt[o].add;
        sgt[lch].val += sgt[o].add * (mid - l + 1);
        sgt[rch].val += sgt[o].add * (r - mid);
        sgt[o].add = 0;
    }
}

inline void build(int o, int l, int r) {
    if(l == r) {
        sgt[o].val = w[ptn[l]];
        return;
    }
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    build(lch, l, mid);
    build(rch, mid + 1, r);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline void add(int o, int l, int r, int ll, int rr, LL v) {
    if(l >= ll && r <= rr) {
        sgt[o].add += v;
        sgt[o].val += v * (r - l + 1);
        return;
    }
    pushdown(o, l, r);
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(ll <= mid) add(lch, l, mid, ll, rr, v);
    if(rr > mid) add(rch, mid + 1, r, ll, rr, v);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline LL query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) {
        return sgt[o].val;
    }
    pushdown(o, l, r);
    LL res = 0;
    int mid = (l + r) >> 1, lch = o << 1, rch = (o << 1) | 1;
    if(ll <= mid) res += query(lch, l, mid, ll, rr);
    if(rr > mid) res += query(rch, mid + 1, r, ll, rr);
    return res;
}

inline void addpoint(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x], v);
}

inline void addsubtree(int x, LL v) {
    add(1, 1, n, dfn[x], dfn[x] + siz[x], v);
}

inline LL querychain(int x) {
    int tp = top[x];
    LL res = 0;
    while(x) {
        res += query(1, 1, n, dfn[tp], dfn[x]);
        x = fa[tp];
        tp = top[x];
    }
    return res;
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) w[i] = readint();
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedge(ut, vt);
    }
    dfs1(1);
    dfs2(1, 1);
    build(1, 1, n);
    while(m--) {
        op = readint();
        xt = readint();
        if(op != 3) at = readint();
        if(op == 1) {
            addpoint(xt, at);
        }
        if(op == 2) {
            addsubtree(xt, at);
        }
        if(op == 3) {
            printf("%lld\n", querychain(xt));
        }
    }
    return 0;
}