点分治、动态点分治
前言
为什么窝点分治切的比FFT还慢T^T(虽说FFT已经忘的一干二净了_(:з)∠)_)
洛谷是个好地方,这群OIer真是niubility!
说回来点分治,分治意味着高效,点分治是一种高效的处理树上路径问题的工具(递归套递归套递归…)
点分治题目最难的可能就是,要猜出他是一道点分治题目了吧QAQ
蒟蒻表示切不动
边码边发现好像从未写过这么长的题解 QAQ
洛谷 P3806 - 【模板】点分治1
链接
https://www.luogu.org/problemnew/show/P3806
思路
考虑当前以rt为根的子树,树上距离为k的点对,有两种情况
要么他们经过rt,要么经过rt的子树的根
对于第二种情况,可以递归解决,因此只考虑如何求出第一种情况的点对数量
第一种情况的点对,满足lca(u, v) = rt && d[u] + d[v] == k
,此处难以解决的是lca(u, v) = rt
,那么就倒过来考虑,考虑求cnt(d[u] + d[v] == k) - cnt(d[u] + d[v] == k && lca(u, v) != rt)
,那么只需要DFS一遍算出d数组,排序一遍,再用O(n)的方法算出cnt(d[u] + d[v] == k)
,再容斥一下即可算出第一种情况的点对数量,然后再递归处理第二种情况即可
但是直接递归的话,若树退化就GG,每次要排序和用O(n)方法处理的n非常大,可以用树形DP中的求树的重心算出点分树的重心,每次递归处理子树重心,这样每次递归处理的n就得到了控制
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 10000 + 15;
typedef long long ll;
const int inf = 0x3f3f3f3f;
struct edge{
int v, w, nxt;
};
int head[N], tot;
bool used[N];
edge e[N << 1];
int sum[N], maxsum[N], d[N], pd;
inline void init(){
tot = 0;
memset(head, -1, sizeof(head));
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
void getRoot(int u, int pre, int size, int& rt){
sum[u] = 1, maxsum[u] = 0;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v] || v == pre) continue;
getRoot(v, u, size, rt);
sum[u] += sum[v];
maxsum[u] = max(maxsum[u], sum[v]);
}
maxsum[u] = max(maxsum[u], size - sum[u]);
if(rt == -1 || maxsum[u] < maxsum[rt]) rt = u;
}
void getDis(int u, int pre, int w){
d[pd++] = w;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v] || v == pre) continue;
getDis(v, u, w + e[i].w);
}
}
int calc(int u, int w, int k){
pd = 0;
getDis(u, -1, w);
sort(d, d + pd);
int ret = 0;
int head = 0, tail = pd - 1;
while(head < tail){
while(d[head] + d[tail] >= k && head < tail){
if(d[head] + d[tail] == k) ret++;
tail--;
}
head++;
}
return ret;
}
int dfs(int u, int n, int k){
int rt = -1;
getRoot(u, -1, n, rt);
used[u] = true;
int ans = calc(u, 0, k);
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
ans -= calc(v, e[i].w, k);
ans += dfs(v, sum[v], k);
}
return ans;
}
int main(){
int n, q;
while(~scanf("%d%d", &n, &q)){
init();
for(int i = 1; i <= n - 1; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
while(q--){
memset(used, 0, sizeof(used));
int k;
scanf("%d", &k);
puts(dfs(1, n, k) ? "AYE" : "NAY");
}
}
}
洛谷 P2664 - 树上游戏
链接
https://www.luogu.org/problemnew/show/P2664
思路
对于以rt为根的子树,有三种情况
① 以rt为一端,其经过其子树的点,子树的点对他的贡献值
② 以rt的子树中的一点为一端,其经过rt,rt对其造成的贡献值
③ 不经过rt
其中的情况三可以分治解决,因此只考虑如何解决情况一和情况二
记v及其子树中点的总数量为sum[v]
,记v点的颜色为color[v]
,记颜色color[v]
的总贡献值为val[color[v]]
,颜色color[v]
出现的次数为cnt[color[v]]
对于情况一,若该点在子树中第一次出现,则其对rt的贡献值为sum[v],因此只需要从rt开始dfs一遍便可计算出子树(包括rt)对rt的贡献值,记为total_sum
,同时更新val数组,再计算情况二
对于情况二,先清空子树(不含rt,此处可以cnt[color[rt]]++
)对total_sum和val数组的影响,以便计算的经过rt的路径不会被子树所影响,再进行DFS。在DFS的过程中,若遍历到节点u,某颜色第一次出现,则记录不同颜色出现的变量other_tot
自增1,rt对u贡献必增加sum[rt] - sum[v]
(v为rt的子树),此时rt对u的总贡献中一部分为other_tot * (sum[rt] - sum[v])
,另一部分的贡献来自于total_sum
减去第一次出现的颜色color[u]
的val,这是因为其已被sum[rt] - sum[v]
涵盖,不减去会重复计算,因此最终
if((++cnt[color[u]]) == 1){
color_tot++;
total_sum -= val[color[u]];
}
ans[u] += (total_sum + (ll)color_tot * other_sz);
记得回溯时需恢复影响
剩余的情况分治处理即可
最后回来讨论一下为什么需要cnt[color[rt]]++
,因为这样可以避免清空子树和计算子树color[rt]第一次出现的影响。color[rt]必然是在rt第一次出现,先++再处理就不会产生影响,而且color[rt]
的影响已计入total_sum
,计算得到的答案依然是正确的
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100000 + 15;
typedef long long ll;
const int inf = 0x3f3f3f3f;
struct edge{
int v, nxt;
};
int head[N], tot;
bool used[N];
edge e[N << 1];
int sum[N], maxsum[N], color[N], cnt[N];
ll ans[N], val[N];
inline void init(){
tot = 0;
memset(head, -1, sizeof(head));
memset(ans, 0, sizeof(ans));
}
inline void addEdge(int u, int v){
e[tot] = edge{v, head[u]};
head[u] = tot++;
}
void getRoot(int u, int pre, int size, int& rt){
sum[u] = 1, maxsum[u] = 0;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v] || v == pre) continue;
getRoot(v, u, size, rt);
sum[u] += sum[v];
maxsum[u] = max(maxsum[u], sum[v]);
}
maxsum[u] = max(maxsum[u], size - sum[u]);
if(rt == -1 || maxsum[u] < maxsum[rt]) rt = u;
}
void initTree(int u, int pre){
cnt[color[u]] = val[color[u]] = 0, sum[u] = 1;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
initTree(v, u);
sum[u] += sum[v];
}
}
void change(int u, int pre, int flag, ll& total_sum){
if((++cnt[color[u]]) == 1){
total_sum = total_sum + flag * sum[u];
val[color[u]] = val[color[u]] + flag * sum[u];
}
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
change(v, u, flag, total_sum);
}
cnt[color[u]]--;
}
void calcRoot(int u, int pre, ll& total_sum){
if((++cnt[color[u]]) == 1){
total_sum += sum[u];
val[color[u]] += sum[u];
}
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
calcRoot(v, u, total_sum);
}
cnt[color[u]]--;
}
void calcSubTree(int u, int pre, int color_tot, int other_sz, ll& total_sum){
if((++cnt[color[u]]) == 1){
color_tot++;
total_sum -= val[color[u]];
}
ans[u] += (total_sum + (ll)color_tot * other_sz);
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
calcSubTree(v, u, color_tot, other_sz, total_sum);
}
if((--cnt[color[u]]) == 0){
color_tot--;
total_sum += val[color[u]];
}
}
void dfs(int u, ll& total_sum){
used[u] = true, total_sum = 0;
initTree(u, -1);
calcRoot(u, -1, total_sum);
ans[u] += total_sum;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
cnt[color[u]]++;
change(v, u, -1, total_sum);
total_sum -= sum[v];
val[color[u]] -= sum[v];
calcSubTree(v, u, 0, sum[u] - sum[v], total_sum);
change(v, u, 1, total_sum);
total_sum += sum[v];
val[color[u]] += sum[v];
cnt[color[u]]--;
}
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
int rt = -1;
getRoot(v, u, sum[v], rt);
dfs(rt, total_sum);
}
}
int main(){
int n;
while(~scanf("%d", &n)){
init();
for(int i = 1; i <= n; i++){
scanf("%d", &color[i]);
}
for(int i = 1; i <= n - 1; i++){
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
int rt = -1;
ll total_sum;
getRoot(1, -1, n, rt);
dfs(rt, total_sum);
for(int i = 1; i <= n; i++){
printf("%lld\n", ans[i]);
}
}
}
洛谷 P3345 - [ZJOI2015]幻想乡战略游戏
链接
https://www.luogu.org/problemnew/show/P3345
思路 —— 动态点分治
首先考虑如何转移答案?
构造点分树,然后爬树高暴力更新,树高logn所以随便搞[滑稽.jpg]
记ans[u][0]
为以u为根的点分树块内的答案总贡献,ans[u][1]
为以u
为根的点分树块内对par[u]
的答案的总贡献,val[u]
为以u为根的点分树块内的军队数量
那么统计以u
为根的答案贡献,只需要一个循环暴力爬树高,边爬边容斥统计答案,边转移根即可。具体来说,记目前循环到根i
,那么将根转移到par[i]
时,答案贡献增量为(ans[par[i]][0] - ans[i][1])
,但是答案是对于根u
而言的,因此还需要计算从par[i]
到u
的答案的增量dist(u, par[i]) * (val[par[i]] - val[i])
(自行画图计算),累加答案即可
改变军队的数量只需要暴力爬树高更新ans
和val
即可
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100100;
const int inf = 0x3f3f3f3f;
typedef long long ll;
struct edge{
int v, w, nxt;
};
int head[N], rthead[N], tot, rttot;
int sum[N], par[N];
int dpt[N], d[N], fa[N][20], maxsum[N];
bool used[N];
edge e[N << 1];
edge rte[N << 1];
ll ans[N][2], val[N];
inline void init(){
tot = rttot = 1;
}
inline int read(){
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
inline void addRootEdge(int u, int v, int w){
rte[rttot] = edge{v, w, rthead[u]};
rthead[u] = rttot++;
}
void initLCA(int u, int pre){
fa[u][0] = pre;
for(int j = 1; j <= 19; j++){
if(fa[u][j - 1] == 0) break;
fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
d[v] = d[u] + e[i].w;
initLCA(v, u);
}
}
inline int lca(int u, int v) {
if(dpt[u] < dpt[v]) swap(u, v);
int tmp = dpt[u] - dpt[v];
for(int k = 0, j = 1; j <= tmp; j <<= 1, k++){
if(tmp & j) u = fa[u][k];
}
while(u != v) {
int j = 0;
while(fa[u][j] != fa[v][j]) j++;
if(j) j--;
u = fa[u][j], v = fa[v][j];
}
return u;
}
int getDist(int u, int v){
return d[u] + d[v] - (d[lca(u, v)] << 1);
}
void getRoot(int u, int pre, int size, int& rt){
sum[u] = 1, maxsum[u] = 0;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
getRoot(v, u, size, rt);
sum[u] += sum[v];
maxsum[u] = max(maxsum[u], sum[v]);
}
maxsum[u] = max(maxsum[u], size - sum[u]);
if(rt == 0 || maxsum[u] < maxsum[rt]) rt = u;
}
void initTree(int u){
used[u] = true;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
int rt = 0;
getRoot(v, u, sum[v], rt);
par[rt] = u;
addRootEdge(u, v, rt);
initTree(rt);
}
}
ll query(int u){
ll ret = ans[u][0];
for(int i = u; par[i]; i = par[i]){
int d = getDist(u, par[i]);
ret += ans[par[i]][0];
ret -= ans[i][1];
ret += (ll)d * (val[par[i]] - val[i]);
}
return ret;
}
void update(int u, int x){
val[u] += x;
for(int i = u; par[i]; i = par[i]){
int d = getDist(u, par[i]);
ans[par[i]][0] += (ll)x * d;
ans[i][1] += (ll)x * d;
val[par[i]] += x;
}
}
ll work(int u){
ll ret = query(u);
for(int i = rthead[u]; i; i = rte[i].nxt){
int v = rte[i].v;
ll tmp = query(v);
if(tmp < ret){
ret = min(ret, work(rte[i].w));
}
}
return ret;
}
int main(){
int n, q;
while(~scanf("%d%d", &n, &q)){
init();
for(int i = 1; i <= n - 1; i++){
int u = read(), v = read(), w = read();
addEdge(u, v, w);
addEdge(v, u, w);
}
initLCA(1, 0);
int rt = 0;
getRoot(1, 0, n, rt);
par[rt] = 0;
initTree(rt);
while(q--){
int u = read(), x = read();
update(u, x);
printf("%lld\n", work(rt));
}
}
return 0;
}
BZOJ 3730 - 震波
链接
https://cn.vjudge.net/problem/HYSBZ-3730
思路1 —— 点分治 + 线段树
与上题一样,通过暴力爬树高的方法进行更新与查询,并用容斥的方法统计答案,本题因为查询与距离相关,可以考虑对每个节点维护一棵动态开点线段树,将该点分治块中的点以距离为下标,插入其价值,这样便可与上题一样查询与更新
#include <cstdio>
#include <iostream>
#include <cstring>
#define lson l, m
#define rson m + 1, r
using namespace std;
const int N = 100100;
const int inf = 0x3f3f3f3f;
struct edge{
int v, nxt;
};
int w[N], head[N], tot;
int sum[N], par[N];
int dpt[N], fa[N][20], d[N], maxsum[N];
bool used[N];
edge e[N << 1];
int root[N][2];
int ls[N << 6], rs[N << 6], seg[N << 6], segtot;
inline void swap(int &x, int &y) {
if(x == y) return;
x = x^y;
y = x^y;
x = x^y;
}
inline int max(const int& a, const int& b){
return a > b ? a : b;
}
inline char get(void) {
static char buf[1000000], *p1 = buf, *p2 = buf;
if (p1 == p2) {
p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
if (p1 == p2) return EOF;
}
return *p1++;
}
inline void read(int &x) {
x = 0; static char c; bool minus = false;
for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') minus = true;
for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (minus) x = -x;
}
inline void write(int x) {
if (!x) return (void) puts("0");
if (x < 0) putchar('-'), x = -x;
static short s[12], t;
while (x) s[++t] = x % 10, x /= 10;
while (t) putchar('0' + s[t--]);
putchar('\n');
}
inline void addEdge(int u, int v){
e[tot] = edge{v, head[u]};
head[u] = tot++;
}
// START -- LCA and dist
void initLCA(int u, int pre){
fa[u][0] = pre;
for(int j = 1; j <= 19; j++){
if(fa[u][j - 1] == 0) break;
fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
initLCA(v, u);
}
}
inline int lca(int x, int y) {
if (dpt[x] < dpt[y]) swap(x, y);
int tmp = dpt[x] - dpt[y];
for (int k = 0, j = 1; j <= tmp; j <<= 1, k++)
if (tmp & j) x = fa[x][k];
while (x != y) {
int j = 0;
while (fa[x][j] != fa[y][j]) j++;
if(j) j--;
x = fa[x][j], y = fa[y][j];
}
return x;
}
int getDist(int u, int v){
return dpt[u] + dpt[v] - (dpt[lca(u, v)] << 1);
}
// END -- LCA and dist
// START -- SegmentTree
inline void newNode(int& o){
o = segtot++;
}
void push(int& o, int pos, int val, int l, int r){
if(o == 0) newNode(o);
seg[o] += val;
if(l == r) return;
int m = (l + r) >> 1;
if(pos <= m) push(ls[o], pos, val, lson);
else push(rs[o], pos, val, rson);
}
int query(int o, int ql, int qr, int l, int r){
if(o == 0) return 0;
if(ql <= l && r <= qr){
return seg[o];
}
int m = (l + r) >> 1;
int ans = 0;
if(ql <= m) ans += query(ls[o], ql, qr, lson);
if(m < qr) ans += query(rs[o], ql, qr, rson);
return ans;
}
// END -- SegmentTree
// START -- Core
void getRoot(int u, int pre, int size, int& rt){
sum[u] = 1, maxsum[u] = 0;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
getRoot(v, u, size, rt);
sum[u] += sum[v];
maxsum[u] = max(maxsum[u], sum[v]);
}
maxsum[u] = max(maxsum[u], size - sum[u]);
if(rt == 0 || maxsum[u] < maxsum[rt]) rt = u;
}
void setSegTree(int u, int pre, int n, int rt, int idx){
push(root[rt][idx], d[u], w[u], 0, n);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
d[v] = d[u] + 1;
setSegTree(v, u, n, rt, idx);
}
}
void initTree(int u, int n){
used[u] = true, d[u] = 0;
setSegTree(u, 0, n, u, 0);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
int rt = 0;
d[v] = 1;
getRoot(v, u, sum[v], rt);
setSegTree(v, u, n, rt, 1);
par[rt] = u;
initTree(rt, n);
}
}
int solveQuery(int u, int n, int d){
int ret = query(root[u][0], 0, d, 0, n);
for(int i = u; par[i] != 0; i = par[i]){
int k = getDist(u, par[i]);
if(d - k < 0) continue;
ret += query(root[par[i]][0], 0, d - k, 0, n);
ret -= query(root[i][1], 0, d - k, 0, n);
}
return ret;
}
void solveUpdate(int u, int n, int delta){
push(root[u][0], 0, delta, 0, n);
for(int i = u; par[i] != 0; i = par[i]){
int d = getDist(u, par[i]);
push(root[par[i]][0], d, delta, 0, n);
push(root[i][1], d, delta, 0, n);
}
}
// END -- Core
inline void init(){
tot = segtot = 1;
}
int main(){
int n, q;
read(n), read(q);
init();
for(int i = 1; i <= n; i++){
read(w[i]);
}
for(int i = 1; i <= n - 1; i++){
int u, v;
read(u), read(v);
addEdge(u, v);
addEdge(v, u);
}
initLCA(1, 0);
int u = 0;
getRoot(1, 0, n, u);
par[u] = 0;
initTree(u, n);
int lstans = 0;
while(q--){
int op, x, y;
read(op), read(x), read(y);
x ^= lstans, y ^= lstans;
if(op){
solveUpdate(x, n, y - w[x]);
w[x] = y;
}else{
lstans = solveQuery(x, n, y);
write(lstans);
}
}
return 0;
}
思路2 —— 点分治 + 树状数组
因为每次查询的区间都是[0,k],故可用树状数组代替线段树,加快程序运行效率
树状数组貌似不存在动态开点的说法吧,但是可用vector开不定长数组,使用resize即可,不用担心爆内存
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 100100;
const int inf = 0x3f3f3f3f;
struct edge{
int v, nxt;
};
int w[N], head[N], tot;
int sum[N], par[N];
int dpt[N], fa[N][20], d[N], maxsum[N];
bool used[N];
edge e[N << 1];
vector<int> tree[N][2];
inline void swap(int &x, int &y) {
if(x == y) return;
x = x^y;
y = x^y;
x = x^y;
}
inline int max(const int& a, const int& b){
return a > b ? a : b;
}
inline char get(void) {
static char buf[1000000], *p1 = buf, *p2 = buf;
if (p1 == p2) {
p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
if (p1 == p2) return EOF;
}
return *p1++;
}
inline void read(int &x) {
x = 0; static char c; bool minus = false;
for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') minus = true;
for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (minus) x = -x;
}
inline void write(int x) {
if (!x) return (void) puts("0");
if (x < 0) putchar('-'), x = -x;
static short s[12], t;
while (x) s[++t] = x % 10, x /= 10;
while (t) putchar('0' + s[t--]);
putchar('\n');
}
inline void addEdge(int u, int v){
e[tot] = edge{v, head[u]};
head[u] = tot++;
}
// START -- LCA and dist
void initLCA(int u, int pre){
fa[u][0] = pre;
for(int j = 1; j <= 19; j++){
if(fa[u][j - 1] == 0) break;
fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
initLCA(v, u);
}
}
inline int lca(int x, int y) {
if(dpt[x] < dpt[y]) swap(x, y);
int tmp = dpt[x] - dpt[y];
for(int k = 0, j = 1; j <= tmp; j <<= 1, k++)
if (tmp & j) x = fa[x][k];
while(x != y) {
int j = 0;
while (fa[x][j] != fa[y][j]) j++;
if(j) j--;
x = fa[x][j], y = fa[y][j];
}
return x;
}
int getDist(int u, int v){
return dpt[u] + dpt[v] - (dpt[lca(u, v)] << 1);
}
// END -- LCA and dist
// START -- BIT
inline int lowbit(int idx){ return (idx & -idx); }
int getSum(vector<int>& vec, int idx){
idx++;
if(idx >= vec.size()) idx = vec.size() - 1;
int sum;
for(sum = 0; idx > 0; idx -= lowbit(idx)){
sum += vec[idx];
}
return sum;
}
void update(vector<int>& vec, int idx, int val){
idx++;
while(idx < vec.size()){
vec[idx] += val;
idx += (idx & -idx);
}
}
// END -- BIT
// START -- Core
void getRoot(int u, int pre, int size, int& rt){
sum[u] = 1, maxsum[u] = 0;
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
getRoot(v, u, size, rt);
sum[u] += sum[v];
maxsum[u] = max(maxsum[u], sum[v]);
}
maxsum[u] = max(maxsum[u], size - sum[u]);
if(rt == 0 || maxsum[u] < maxsum[rt]) rt = u;
}
void setSegTree(int u, int pre, int rt, int idx){
update(tree[rt][idx], d[u], w[u]);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(v == pre || used[v]) continue;
d[v] = d[u] + 1;
setSegTree(v, u, rt, idx);
}
}
void initTree(int u, int n){
used[u] = true, d[u] = 0;
setSegTree(u, 0, u, 0);
for(int i = head[u]; i; i = e[i].nxt){
int v = e[i].v;
if(used[v]) continue;
int rt = 0;
d[v] = 1;
getRoot(v, u, sum[v], rt);
tree[rt][0].resize(sum[v] + 5);
tree[rt][1].resize(sum[v] + 5);
setSegTree(v, u, rt, 1);
par[rt] = u;
initTree(rt, n);
}
}
int solveQuery(int u, int d){
int ret = getSum(tree[u][0], d);
for(int i = u; par[i] != 0; i = par[i]){
int k = getDist(u, par[i]);
if(d - k < 0) continue;
ret += getSum(tree[par[i]][0], d - k);
ret -= getSum(tree[i][1], d - k);
}
return ret;
}
void solveUpdate(int u, int delta){
update(tree[u][0], 0, delta);
for(int i = u; par[i] != 0; i = par[i]){
int d = getDist(u, par[i]);
update(tree[par[i]][0], d, delta);
update(tree[i][1], d, delta);
}
}
// END -- Core
inline void init(){
tot = 1;
}
int main(){
int n, q;
read(n), read(q);
init();
for(int i = 1; i <= n; i++){
read(w[i]);
}
for(int i = 1; i <= n - 1; i++){
int u, v;
read(u), read(v);
addEdge(u, v);
addEdge(v, u);
}
initLCA(1, 0);
int u = 0;
getRoot(1, 0, n, u);
tree[u][0].resize(n + 5);
par[u] = 0;
initTree(u, n);
int lstans = 0;
while(q--){
int op, x, y;
read(op), read(x), read(y);
x ^= lstans, y ^= lstans;
if(op){
solveUpdate(x, y - w[x]);
w[x] = y;
}else{
lstans = solveQuery(x, y);
write(lstans);
}
}
return 0;
}