树上启发式合并

树上启发式合并

\(Dsu ~ on ~ Tree\)

是一种很暴力的东西捏

可以说是 重儿子 性质的一种 利用手段,结构复杂度是 \(O (N \log N)\)

一般用于处理 不带修的子树查询 问题(\(aka.\) 静态链分治?)

其利用了 儿子子树信息 可以 继承到父亲 上这个性质

但由于 儿子之间子树信息多是不可合并的,也就是说 切换儿子 时 统计的信息 需要清空

于是当遍历完一个结点的 所有儿子 时,最多 只能选择 其中一个儿子的子树信息 保留

这个选择就 十分重要,考虑到重儿子 \(Siz\) 最大,”携带的信息多“,把它保留

而最终计算当前节点的答案时将其它子树的信息 重新统计

若对于 一个结点计算答案 操作是 \(O(1)\) 的,则这样选择后总时间复杂度为 \(O(N \log N)\)

证明 可以从 每个点被访问了多少次 这个方向入手

注意到我们每次会保留 重儿子子树,重新统计 轻儿子子树

而一个点 到根的路径中 至多有 \(O(\log N)\)轻重链切换 的过程

也就是说,每个点只会在至多 \(O (\log N)\) 个点的 轻儿子子树 内,被统计 \(O (\log N)\)

\(O (\log N)\) 个点就是每次 重链链头的父亲,即 切换点

于是一共 \(O(N)\) 个点,至多被统计 \(O(N \log N)\) 次,复杂度正确


CF600E Lomsat gelral

板子题,又调半天...

套用上方的流程,用数组记录 每个颜色出现的次数

同时两个变量记录 颜色最大出现次数最大次数颜色编号和

先计算 轻儿子答案,删除贡献,然后算 重儿子答案保留贡献

暴力加上轻儿子的贡献,计算当前点答案 即可

#include <bits/stdc++.h>

const int MAXN = 100005;

using namespace std;

struct Edge {
	int to, nxt;
} E[MAXN << 1];

int H[MAXN], tot;

inline void Add_Edge (const int u, const int v) {
	E[++ tot] = {v, H[u]}, H[u] = tot;
	E[++ tot] = {u, H[v]}, H[v] = tot;
}

int Siz[MAXN], Son[MAXN];

inline void DFS1 (const int x, const int f) {
	Siz[x] = 1;
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f)
			DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Siz[E[i].to] > Siz[Son[x]] ? Son[x] = E[i].to : Son[x];
}

int Cnt[MAXN], Col[MAXN];
long long Ans[MAXN];

long long Sum;
int Max;

inline void Add (const int x, const int f) {
	Cnt[Col[x]] ++ ;
	if (Cnt[Col[x]] > Max) Sum = Col[x], Max = Cnt[Col[x]];
	else if (Cnt[Col[x]] == Max) Sum += Col[x];
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Add (E[i].to, x);
}

inline void Del (const int x, const int f) {
	Cnt[Col[x]] -- ;
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Del (E[i].to, x);
}

inline void DFS2 (const int x, const int f) {
	if (!x) return ;
		
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			DFS2 (E[i].to, x), Del (E[i].to, x), Sum = Max = 0;
	
	if (Son[x]) DFS2 (Son[x], x);
	
	Cnt[Col[x]] ++ ;
	if (Cnt[Col[x]] > Max) Sum = Col[x], Max = Cnt[Col[x]];
	else if (Cnt[Col[x]] == Max) Sum += Col[x];
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			Add (E[i].to, x);
	
	Ans[x] = Sum;
}

int N;

int u, v;

int main () {
	
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> N;
	
	for (int i = 1; i <= N; ++ i) cin >> Col[i];
	
	for (int i = 2; i <= N; ++ i) cin >> u >> v, Add_Edge (u, v);
	
	DFS1 (1, 0), DFS2 (1, 0);
	
	for (int i = 1; i <= N; ++ i) cout << Ans[i] << ' ';
	
	return 0;
}

CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

据说是 树上启发式合并发明者 出的题?确实 很启发

考虑 回文串 的性质,若想 重排得到回文串,则 至多有 \(1\) 种字符出现奇数次

要求 最长路径,显然把路径长差分成 \(Dep_u + Dep_v - Dep_{LCA}\)

可以想到 点分治的思想,开一个桶记录 每种情况下的最大深度,然后合并

为什么这道题不能直接用 点分治

注意到我们要求 每个点子树内最长路径

点分治不能保证原树的子树结构 来遍历(每次递归到子树的 重心

不能正确求出答案

具体什么是 每种情况

考虑 \(\sum = 22\),故 对于每任意路径,我们可以记录 每种字符出现的次数奇偶

压缩成一个 unsigned int 表示,而这样的 字符出现情况 一共 \(2 ^ {22}\)

为什么这么做?

我们设一条路径上 字符出现情况 表示为数 \(x\)

容易发现,若这条路径是 \(Dokhtar-kosh\) 路径,则有 \(x = 0\)\(x = 2 ^ k ~ (k \in [0, 21])\)

\(x\) 二进制下存在 至多一位为 \(0\),即 至多 \(1\) 种字符出现奇数次

显然,异或可差分,于是我们可以记录 从根到每个点 路径的 字符出现情况 \(Dis_x\)

于是 \(u \to v\)字符出现情况 就可以表示为 \(Dis_u \oplus Dis_v \oplus Dis_{LCA} \oplus Dis_{LCA}\)

又考虑 \(x \oplus x = 0\),于是 \(Dis_u \oplus Dis_v \oplus Dis_{LCA} \oplus Dis_{LCA} = Dis_u \oplus Dis_v\)

于是我们在 \(O(N)\) 预处理 后 可以 \(O(1)\) 求出 任意路径字符出现情况

回到题目,由于 路径长 的计算需要 \(LCA\),我们又需求出 所有点子树的答案

故容易想到 遍历每个点作为 \(LCA\),计算贡献

先考虑 简单暴力,我们尝试 枚举当前点 \(x\) 的儿子子树,获取 儿子子树所有点的 \(Dis\)

遍历并查询 对应桶 内是否有值,即 能否与其它儿子子树中的点构成合法路径

更新答案,此处路径显然经过 当前点 \(x\),即 \(LCA = x\),路径长易得

然后用 这棵儿子子树所有点的 \(Dis\) 更新桶

注意,我们需要更新所有满足 \(Dis \oplus p = 0\)\(Dis \oplus p = 2 ^ k\) 的桶 \(p\),显然有 \(O (\sum)\)

于是这一步操作的 时间复杂度\(O(\sum)\)

处理完 当前点 \(x\) 后,清空桶等信息,递归进儿子子树处理 即可

显然,这样的时间复杂度是 \(O(N ^ 2 \sum)\) 的,过不了一点

但是这个结构我们很熟悉,就是上面 启发式合并 的结构,可以用同样的办法优化

先递归求所有轻儿子答案,清空信息,然后 求重儿子答案保留信息

暴力添加轻儿子贡献,计算当前点答案,即可把时间复杂度优化到 \(O (N \log N \sum)\)

看上去挺卡的其实,启发式合并常数也蛮大,但是 实际上不卡常

细节极少,适合用来熟悉 启发式合并 的基本结构

#include <bits/stdc++.h>

const int MAXN = 500005;
const int MAXK = (1 << 22) + 5;

using namespace std;

struct Edge {
	int to, nxt;
} E[MAXN << 1];

int H[MAXN], F[MAXN], tot;

inline void Add_Edge (const int u, const int v) {
	E[++ tot] = {v, H[u]}, H[u] = tot;
	E[++ tot] = {u, H[v]}, H[v] = tot;
}

char Val[MAXN];

int Siz[MAXN], Son[MAXN], Dep[MAXN];

unsigned int Dis[MAXN];

inline void DFS1 (const int x, const int f) {
	Siz[x] = 1, Dep[x] = Dep[F[x] = f] + 1;
	if (x > 1) Dis[x] = Dis[f] ^ (1 << (Val[x] - 'a'));
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f)
			DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Siz[E[i].to] > Siz[Son[x]] ? Son[x] = E[i].to : Son[x];
}

int Max[MAXK], Ans[MAXN];

struct Node {
	unsigned int dis;
	int dep;
};

vector <Node> P;

inline void Del (const int x, const int f) {
	for (int i = 0; i <= 21; ++ i) Max[Dis[x] ^ (1 << i)] = 0;
	Max[Dis[x]] = 0;
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Del (E[i].to, x);
}

inline void Add (const int x, const int f) {
	P.push_back ({Dis[x], Dep[x]});
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Add (E[i].to, x);
}

inline void DFS2 (const int x, const int f) {
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			DFS2 (E[i].to, x), Del (E[i].to, x), Ans[x] = max (Ans[x], Ans[E[i].to]);
	
	if (Son[x]) DFS2 (Son[x], x), Ans[x] = max (Ans[x], Ans[Son[x]]);
	
	if (Max[Dis[x]]) Ans[x] = max (Ans[x], Max[Dis[x]] - Dep[x]);
		
	for (int i = 0; i <= 21; ++ i) Max[Dis[x] ^ (1 << i)] = max (Max[Dis[x] ^ (1 << i)], Dep[x]);
	Max[Dis[x]] = max (Max[Dis[x]], Dep[x]);
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x]) {
			P.clear (), Add (E[i].to, x);
			for (auto k : P) if (Max[k.dis]) Ans[x] = max (Ans[x], k.dep + Max[k.dis] - (Dep[x] << 1));
			for (auto k : P) {
				for (int p = 0; p <= 21; ++ p)
					Max[k.dis ^ (1 << p)] = max (Max[k.dis ^ (1 << p)], k.dep);
				Max[k.dis] = max (Max[k.dis], k.dep);
			}
		}
}

int N;
int x;

int main () {
	
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> N;
	
	for (int i = 2; i <= N; ++ i)
		cin >> x >> Val[i], Add_Edge (x, i);
	
	DFS1 (1, 0), DFS2 (1, 0);
	
	for (int i = 1; i <= N; ++ i) cout << Ans[i] << ' ';
	
	return 0;
}

支配对

考虑一些 点对统计 的问题

存在点对 \((u_1, v_1), (u_2, v_2)\),当统计到 \((u_2, v_2)\) 时,我们一定能统计到 \((u_1, v_1)\)

并且它们对最终答案有 一样贡献时

我们可以声称 它们存在 支配关系,其中 \((u_1, v_1)\) 支配了 \((u_2, v_2)\)

通常称一个点对为 支配对 当且仅当 它没有被任何其它点对支配

我们可以只考虑 计数支配对 即可

树上的支配对问题常见的就是下面的 树上保留区间点 问题

一般树上问题与 编号在区间内的点两两间 \(LCA\) 有关,而 \(LCA\) 只有 本质不同 \(N\) 个点

常见思路即 启发式合并,加入点 \(x\) 时 查找 编号的前驱后继 产生 支配对

显然,前驱后继产生的 \(u, v\) 最为接近

这样一共可以找出 \(O (N \log N)\) 对点对,支配了原有 \(O (N ^ 2)\) 对点对的贡献

具体可以看下面例题


Luogu P7880 [Ynoi2006] rldcot

给定一棵 带权树,多次询问 区间,求 区间内任意点对的 \(LCA\) 深度种类

即询问 \(S = Dep_{LCA (u, v) |u, v \in [L, R]}, |S|\)

不妨设 \(u \le v\),考虑若存在 \(u_2 \le u_1 \le v_1 \le v_2\),且 \(LCA (u_1, v_1) = LCA (u_2, v_2)\)

则 统计到 \((u_2, v_2)\) 时,我们一定能统计到 \((u_1, v_1)\),并且它们 会贡献一种同样的深度

显然,\((u_1, v_1)\) 支配了 \((u_2, v_2)\),于是我们可以统计 每个 \(LCA\) 对应的支配对

容易证明,若 \(u, v\)\(LCA = x\)支配对

\(u\) 为 与 \(v\) 不在同一个 \(x\) 的儿子子树前驱,同理,\(v\)\(u\)后继

现在考虑如何求出 \(LCA = x\)支配对

显然,我们可以遍历儿子子树,维护一个 std::set存储之前儿子子树里的点

同时 拿出当前儿子子树的点,在 std::set 里查找 前驱后继 即可

但是 不同儿子子树std::set 显然不能 继承,每次暴力遍历子树就是 \(O(N ^ 2)\) 的了

于是又可以考虑 启发式合并 的结构,求解 轻儿子清空,求解 重儿子保留,暴力加轻儿子

得到所有支配对之后,离线询问,与 支配对 同样按 左端排序

扫描线扫左端点,每次对 所有相同深度支配对 的右端点取 \(\min\) 插到一个 权值树状数组

我们刚刚求的是 \(LCA = x\)支配对,可能有 深度相同的 情况

然后 直接查询问右端点 即可,注意深度需要预处理后 离散化,然后要开 long long

还是比较好写的,不卡常

#include <bits/stdc++.h>

const int MAXN = 100005;
const int MAXQ = 500005;

using namespace std;

struct Edge {
	int to, nxt, w;
} E[MAXN << 1];

int H[MAXN], F[MAXN], tot;

inline void Add_Edge (const int u, const int v, const int w) {
	E[++ tot] = {v, H[u], w}, H[u] = tot;
	E[++ tot] = {u, H[v], w}, H[v] = tot;
}

struct Que {
	int R, id;
};

struct Inv { 
	int R, C;
};

int N, M;

namespace BIT {
	int T[MAXN << 1];
	
	#define lowbit(x) (x & -x)
	
	inline void Add (int x) {
		while (x <= N + 2) ++ T[x], x += lowbit (x);
	}
	
	inline void Del (int x) {
		while (x <= N + 2) -- T[x], x += lowbit (x);
	}
	
	inline int Sum (int x) {
		int Ret = 0;
		while (x) Ret += T[x], x -= lowbit (x);
		return Ret;
	}
}

int Siz[MAXN], Son[MAXN], Pos[MAXN], dep[MAXN], Ans[MAXQ];

long long Dep[MAXN];

inline void DFS1 (const int x, const int f, const int w = 0) {
	Siz[x] = 1, Dep[x] = Dep[F[x] = f] + w;
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f)
			DFS1 (E[i].to, x, E[i].w), Siz[x] += Siz[E[i].to], Siz[E[i].to] > Siz[Son[x]] ? Son[x] = E[i].to : Son[x];
}

vector <Inv> P[MAXN];
vector <Que> Q[MAXN];
set <int> S;

inline void AddNode (const int x, const int lcadep) {
	auto it = S.lower_bound (x);
	if (it != S.end ()) P[x].push_back ({* it, lcadep});
	if (it != S.begin ()) -- it, P[* it].push_back ({x, lcadep});
}

inline void Calc (const int x, const int f, const int lcadep) {
	AddNode (x, lcadep);
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Calc (E[i].to, x, lcadep);
}

inline void Add (const int x, const int f) {
	S.insert (x);
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Add (E[i].to, x);
}

inline void DFS2 (const int x, const int f) {
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			DFS2 (E[i].to, x), S.clear ();
	
	if (Son[x]) DFS2 (Son[x], x), AddNode (x, dep[x]);
	
	P[x].push_back ({x, dep[x]}), S.insert (x);
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			Calc (E[i].to, x, dep[x]), Add (E[i].to, x);
}

int Cnt;
long long Tmp[MAXN];

inline void Unique (int * Des, long long * Src) {
	for (int i = 1; i <= N; ++ i) Tmp[i] = Src[i];
	
	sort (Src + 1, Src + N + 1);
	
	Cnt = unique (Src + 1, Src + N + 1) - Src - 1;
	
	for (int i = 1; i <= Cnt; ++ i) Pos[i] = N + 1;
	
	for (int i = 1; i <= N; ++ i) Des[i] = lower_bound (Src + 1, Src + Cnt + 1, Tmp[i]) - Src;
}

int u, v, w;

int main () {
	
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> N >> M;
	
	for (int i = 2; i <= N; ++ i)	
		cin >> u >> v >> w, Add_Edge (u, v, w);
		
	DFS1 (1, 0), Unique (dep, Dep), DFS2 (1, 0);
	
	for (int i = 1; i <= M; ++ i)	
		cin >> u >> v, Q[u].push_back ({v, i});
	
	for (int i = N; i >= 1; -- i) {
		for (auto p : P[i]) BIT::Del (Pos[p.C]), Pos[p.C] = min (Pos[p.C], p.R), BIT::Add (Pos[p.C]);
		for (auto q : Q[i]) Ans[q.id] = BIT::Sum (q.R);
	}
	
	for (int i = 1; i <= M; ++ i) cout << Ans[i] << '\n';
	
	return 0;
}

Luogu P8528 [Ynoi2003] 铃原露露

和上面那个东西的套路很像啊

加了一个 排列 的套路,直接在 插入点集的时候插 \(a_x\)

AddNode 的时候往 std::vector 里面推 \(a_x\) 而非 \(x\) 即可,这是简单的

考虑 \(LCA\) 这边的问题,容易发现,加了排列这个东西之后 \(a_x, a_y, a_{lca}\)大小关系不定

区间子区间问题,实质上限制在一个 二维平面的矩形 上(左右端点分别一个维度)

于是我们可以考虑 扫描线扫一维,然后用 线段树维护一维

左右端点等价的,但是扫 右端点 的话扫描线就是 从左往右的,赢!

我们考虑如下 三种 大小关系,可以钦定 \(a_x < a_y\)

  1. \(a_x < a_{lca} < a_y\),不用去单独考虑 \(LCA\),显然只需要 \(L < a_x, a_y < R\) 即可
  2. \(a_{lca} < a_x < a_y\),容易发现,右端点 \(\ge a_y\) 时,左端点 不能取 \((a_{lca}, a_x]\)
  3. \(a_x < a_y < a_{lca}\),容易发现,右端点 \(\in [a_y, a_{lca}]\) 时,左端点 哪儿都不能取(不能取 \([1, a_x]\)

于是我们对于每对 \(x, y\),处理出 不能取的部分,挂在对应右端点上

扫描线扫到对应点时,把不能取的部分在 线段树上区间标记

我们需要统计的就是 没有被标记到的部分,又由于我们实际上要一个 子矩形内的信息

于是线段树需要维护 历史信息(扫描线 + 时间维 = 矩形)

可以考虑把区间标记变成 区间加,容易发现,矩形内的数始终非负

于是统计 子矩形内 没有标记的点 就是统计 子矩形内 \(0\) 的个数,或说 最小值个数

写一个板子就行

然后上面说的是 对于每对 \(x, y\),这有 \(O(N ^ 2)\) 个,会寄

于是考虑 支配对,和上面那道题 一模一样,枚举 \(LCA\),然后枚举儿子子树内点

然后找 不同儿子子树内的 前后缀点 即可(注意到这里的排序按 \(a_x\) 排,而非 \(x\)

好写不卡常!但是代码较长

#include <bits/stdc++.h>

const int MAXN = 200005;

using namespace std;

int N, Q;

int Cnt = 0;

int A[MAXN], U[MAXN];

struct Edge {
	int to, nxt;
} E[MAXN << 1];

int H[MAXN], F[MAXN], tot;

inline void Add_Edge (const int u, const int v) {
	E[++ tot] = {v, H[u]}, H[u] = tot;
	E[++ tot] = {u, H[v]}, H[v] = tot;
}

int Siz[MAXN], Son[MAXN];

inline void DFS1 (const int x, const int f) {
	Siz[x] = 1;
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f)
			DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Siz[E[i].to] > Siz[Son[x]] ? Son[x] = E[i].to : Son[x];
}

struct Node {
	int L, R, v;
};

struct Ques {
	int L, id;
};

std::set <int> S;

std::vector <Node> P[MAXN];
std::vector <Ques> I[MAXN];

inline void InsNode (const int x, const int y, const int z) {
	if (z < x) P[y].push_back ({z + 1, x, 1}), ++ Cnt;
	if (z > y) P[y].push_back ({1, x, 1}), P[z].push_back ({1, x, -1}), Cnt += 2;
}

inline void AddNode (const int x, const int z) {
	auto it = S.lower_bound (A[x]);
	if (it != S.end ()) InsNode (A[x], * it, A[z]);
	if (it != S.begin ()) -- it, InsNode (* it, A[x], A[z]);
}

inline void Add (const int x, const int f, const int z) {
	AddNode (x, z);
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Add (E[i].to, x, z);
}

inline void Ins (const int x, const int f) {
	S.insert (A[x]);
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f) Ins (E[i].to, x);
}

inline void DFS2 (const int x, const int f) {
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			DFS2 (E[i].to, x), S.clear ();
	
	if (Son[x]) DFS2 (Son[x], x);
	
	S.insert (A[x]), AddNode (x, x);
	
	for (int i = H[x]; i; i = E[i].nxt)
		if (E[i].to != f && E[i].to != Son[x])
			Add (E[i].to, x, x), Ins (E[i].to, x);
}

namespace SegTree {
	struct node {
		int L, R;
		int Min, Tag, Cnt, Ctg;
		long long Sum;
	} T[MAXN << 2];
	
	#define LC (x << 1)
	#define RC (x << 1 | 1)
	#define M ((T[x].L + T[x].R) >> 1)
	
	inline void Maintain (const int x) {
		T[x].Sum = T[LC].Sum + T[RC].Sum;
		T[x].Min = min (T[LC].Min, T[RC].Min);
		T[x].Cnt = (T[LC].Min == T[x].Min) * T[LC].Cnt + (T[RC].Min == T[x].Min) * T[RC].Cnt;
	}
	
	inline void PutAdd (const int x, const int tag) {
		T[x].Min += tag, T[x].Tag += tag;
	}
	
	inline void PutCnt (const int x, const int tag) {
		T[x].Sum += 1ll * tag * T[x].Cnt, T[x].Ctg += tag;
	}
	
	inline void Update (const int x) {
		if (T[x].Tag) PutAdd (LC, T[x].Tag), PutAdd (RC, T[x].Tag);
		if (T[x].Ctg && T[x].Min == T[LC].Min) PutCnt (LC, T[x].Ctg);
		if (T[x].Ctg && T[x].Min == T[RC].Min) PutCnt (RC, T[x].Ctg);
		T[x].Tag = T[x].Ctg = 0;
	}
	
	inline void Build (const int L, const int R, const int x = 1) {
		T[x].L = L, T[x].R = R, T[x].Cnt = R - L + 1;
		if (L == R) return ;
		Build (L, M, LC), Build (M + 1, R, RC), Maintain (x);
	}
	
	inline void Add (const int L, const int R, const int v, const int x = 1) {
		if (L >  T[x].R || T[x].L >  R) return ;
		if (L <= T[x].L && T[x].R <= R) return PutAdd (x, v);
		Update (x), Add (L, R, v, LC), Add (L, R, v, RC), Maintain (x);
	}
	
	inline void Upd (const int L, const int R, const int x = 1) {
		if (L >  T[x].R || T[x].L >  R) return ;
		if (L <= T[x].L && T[x].R <= R) return (T[x].Min == 0) ? PutCnt (x, 1) : void ();
		Update (x), Upd (L, R, LC), Upd (L, R, RC), Maintain (x);
	}
	
	inline long long Que (const int L, const int R, const int x = 1) {
		if (L >  T[x].R || T[x].L >  R) return 0;
		if (L <= T[x].L && T[x].R <= R) return T[x].Sum;
		Update (x); return Que (L, R, LC) + Que (L, R, RC);
	}
	
}

int L, R;

long long Ans[MAXN];

int main () {
	
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> N >> Q;
	
	for (int i = 1; i <= N; ++ i) 
		cin >> A[i], U[A[i]] = i;
	
	for (int i = 2; i <= N; ++ i)
		cin >> F[i], Add_Edge (F[i], i);
	
	DFS1 (1, 0), DFS2 (1, 0), SegTree::Build (1, N);
	
	for (int i = 1; i <= Q; ++ i) 
		cin >> L >> R, I[R].push_back ({L, i});
	
	for (int i = 1; i <= N; ++ i) {
		for (auto p : P[i]) SegTree::Add (p.L, p.R, p.v);
		SegTree::Upd (1, i);
		for (auto p : I[i]) Ans[p.id] = SegTree::Que (p.L, i);
	}
	
	for (int i = 1; i <= Q; ++ i) cout << Ans[i] << '\n';
	
	return 0;
}
posted @ 2024-05-29 08:38  FAKUMARER  阅读(14)  评论(0编辑  收藏  举报