1. 程式人生 > 實用技巧 >[luogu P6041]「ACOI2020」布丁暗殺計劃 題解

[luogu P6041]「ACOI2020」布丁暗殺計劃 題解

嗯...感覺這題自己寫噁心了。覺得複雜度應該是對的,但是不使用部分分做法過不了,不知為什麼,估計是常數的問題。

這題一開始覺得像dsu on tree,但是我不會。

於是提供兩種做法:

  1. bfs + 莫隊(複雜度不正確,需使用部分分做法以及吸氧)
  2. bfs + 主席樹(複雜度正確,需使用部分分,無需吸氧可過,但是在吸氧條件下跑得比上面的慢)

先說一下基礎的bfs問題。

常見的樹上問題好像用dfs序解決的比較多(可能只是蒟蒻做題太少)。

利用dfs序,我們可以解決一些子樹問題。利用dfs序上一個節點後面一段連續區間都在它的子樹當中。

但是這道題因為需要統計某一級的所有兒子的資訊。請原諒蒟蒻沒想到怎麼用dfs來做。

所以就有了bfs的做法,根據bfs的性質,我們可以想到,一個點的某一級的所有兒子反映到bfs序上一定也是一段連續的區間。

利用倍增和二分,我們可以在\(O(log\,n + 2\, log^2n )\)的時間內求出每個詢問所需要統計的bfs序區間,這個區間的k級祖先是同一個。第一個log是倍增找祖先的,第二個\(log^2\)是二分確定左端點的,第三個\(log^2\)是二分確定右端點的,二分的判斷方式是看mid的k級祖先是不是也是該祖先,所以是\(log^2\)的。

然後問題轉化為給定若干組左端點右端點,在一段序列上統計一些資訊。這一看就很莫隊對不對。

但是5e5的資料可能會阻擋一些人的想法,但是我比較頭鐵。本著資料一定是隨機的想法

,我交了一發發現只有第二個部分分不可過。

所以果斷打上部分分做法水過。

部分分做法

很顯然一個節點的某一級的後代只會有一個。所以每個詢問直接倍增找他的祖先,如果顏色相同答案就是該節點的\(d\),否則就是0 。

但是本著對人民負責(懶得寫別的題)的原則,我想了想怎麼做複雜度是對的。

然後發現處理序列問題某一段區間的問題可以字首和,但是空間開不下,所以主席樹也挺行的。

然後把主席樹的板子改一改就可做了。

這個主席樹是對顏色開的,即 \(l==r\)\(sum[u]\) 對應的就是在bfs序上 1 到 某一下標 顏色為 \(l\) 的權值的總和,支援單點修改單點查詢即可。

注意我的程式碼裡主席樹區間只是\([1,n]\)

的,如果有顏色大於\(n\)的話會錯。碼的時候忘了,現在懶得改了。正確的做法是讀入時記錄一下最大的顏色mx,然後建樹建\([1,mx]\)

這樣我們就可以用\(O(log \,n)\)複雜度做到線上回答詢問。

複雜度瓶頸應該是\(O(q \, log^2n)\),應該是可過的,但是不知道為何鏈的情況就是有兩個點會T 。

感覺是常數的問題,看到題解裡\(O(n \, log \, n)\)長剖的大兄弟都過不去我就不想調了。

code1(bfs + 莫隊):

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 5e5 + 10;
inline int read() {
	int s = 0, w = 1;
	char c = getchar();
	while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
	while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getchar();
	return s * w;
}
struct edge {
	int nex;
	int to;
} e[maxn << 1];
int head[maxn];
int tot;
void Add(int u, int v) {
	e[++tot] = (edge) { head[u], v }, head[u] = tot;
}
int n, m;
int bfsclock;
int deg[maxn];
int c[maxn];
int d[maxn];
int bfn[maxn];
int idex[maxn];
int sum[maxn];
int ans[maxn];
int fa[maxn][26];
int bel[maxn];
struct node {
	int c;
	int l;
	int r;
	int id;
	bool operator < (const node &x) const {
		if (bel[l] != bel[x.l]) return l < x.l;
		if (bel[l] & 1) return r < x.r;
		return r > x.r;
	}
} Q[maxn];
int Find(int u, int k) {
	int res = 0;
	while (k) {
		if (k & 1) u = fa[u][res];
		res++, k >>= 1;
	}
	return u;
}
void PreWork(int u) {
	for (int i = 1; i <= 25; ++i) {
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for (int i = head[u]; i; i = e[i].nex) {
		int v = e[i].to;
		if (v == fa[u][0]) continue;
		PreWork(v);
	}
}
queue<int> que;
bool vis[maxn];
void bfs() {
	que.push(1);
	while (!que.empty()) {
		int u = que.front();
		que.pop();
		bfn[u] = ++bfsclock;
		idex[bfsclock] = u;
		for (int i = head[u]; i; i = e[i].nex) {
			int v = e[i].to;
			if (!vis[v]) que.push(v), vis[v] = 1;
		}
	}
}
void init() {
	int s = sqrt(n), cnt = 0;
	for (int l = 1, r; l + s - 1 <= n; l += s) {
		r = l + s - 1;
		cnt++;
		for (int i = l; i <= r; ++i) bel[i] = cnt;
	}
	if (s * cnt < n) {
		int l = s * cnt + 1;
		int r = n;
		cnt++;
		for (int i = l; i <= r; ++i) bel[i] = cnt;
	}
}
void putin(int x) {
	sum[c[idex[x]]] += d[idex[x]];
}
void del(int x) {
	sum[c[idex[x]]] -= d[idex[x]];
}
int main() {
	cin >> n >> m;
	init();
	for (int i = 1; i <= n; ++i) c[i] = read();
	for (int i = 1; i <= n; ++i) d[i] = read();
	for (int i = 2; i <= n; ++i) {
		fa[i][0] = read();
		Add(fa[i][0], i);
		deg[i]++;
		deg[fa[i][0]]++;
	}
	PreWork(1);
	bfs();
	int cnt1 = 0, cnt2 = 0;
	for (int i = 1; i <= n; ++i) {
		if (deg[i] == 1) cnt1++;
		if (deg[i] == 2) cnt2++;
	}
	if (cnt1 == 2 && cnt2 == n - 2) {
		for (int i = 1; i <= m; ++i) {
			int u = read(), k = read();
			int faa = Find(u, k);
			if (c[u] == c[faa]) cout << d[u] << '\n';
			else cout << 0 << '\n';
		}
		return 0;
	}
	for (int i = 1; i <= m; ++i) {
		int u = read(), k = read();	
		int faa = Find(u, k);
		int l = 1, r = bfn[u] - 1, mid;
		Q[i].id = i;
		Q[i].c = c[faa];
		while (l <= r) {
			mid = (l + r) >> 1;
			if (Find(idex[mid], k) == faa) r = mid - 1;
			else l = mid + 1;
		}
		Q[i].l = l;
		l = bfn[u] + 1, r = n;
		while (l <= r) {
			mid = (l + r) >> 1;
			if (Find(idex[mid], k) == faa) l = mid + 1;
			else r = mid - 1;
		}
		Q[i].r = l - 1;
	}
	sort(Q + 1, Q + 1 + m);
	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		node q = Q[i];
		while (l > q.l) putin(--l);
		while (r < q.r) putin(++r);
		while (l < q.l) del(l++);
		while (r > q.r) del(r--);
		ans[q.id] = sum[q.c];
	}
	for (int i = 1; i <= m; ++i) cout << ans[i] << '\n';
	return 0;
}

code2(bfs + 主席樹):

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 10;
inline int read() {
	int s = 0, w = 1;
	char c = getchar();
	while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
	while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getchar();
	return s * w;
}
struct edge {
	int nex;
	int to;
} e[maxn << 1];
int head[maxn];
int tot;
void Add(int u, int v) {
	e[++tot] = (edge) { head[u], v }, head[u] = tot;
}
int n, m;
int bfsclock;
int son[maxn];
int c[maxn];
int d[maxn];
int bfn[maxn];
int idex[maxn];
int fa[maxn][26];
int Find(int u, int k) {
	int res = 0;
	while (k) {
		if (k & 1) u = fa[u][res];
		res++, k >>= 1;
	}
	return u;
}
void PreWork(int u) {
	for (int i = 1; i <= 25; ++i) {
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for (int i = head[u]; i; i = e[i].nex) {
		int v = e[i].to;
		if (v == fa[u][0]) continue;
		PreWork(v);
	}
}
queue<int> que;
bool vis[maxn];
void bfs() {
	que.push(1);
	while (!que.empty()) {
		int u = que.front();
		que.pop();
		bfn[u] = ++bfsclock;
		idex[bfsclock] = u;
		for (int i = head[u]; i; i = e[i].nex) {
			int v = e[i].to;
			if (!vis[v]) que.push(v), vis[v] = 1;
		}
	}
}

//主席樹
int cnt;
int rt[maxn];
ll sum[maxn << 5];
int ls[maxn << 5];
int rs[maxn << 5];
int build(int l, int r) {
	int root = ++cnt;
	sum[root] = 0;
	if (l == r) return root;
	int mid = (l + r) >> 1;
	ls[root] = build(l, mid);
	rs[root] = build(mid + 1, r);
	return root;
}
int modify(int pre, int l, int r, int pos, int val) {
	int root = ++cnt;
	ls[root] = ls[pre];
	rs[root] = rs[pre];
	sum[root] = sum[pre] + val;
	if (l == r) return root;
	int mid = (l + r) >> 1;
	if (pos <= mid) ls[root] = modify(ls[pre], l, mid, pos, val);
	else rs[root] = modify(rs[pre], mid + 1, r, pos, val);
	return root;
}
int query(int u, int v, int l, int r, int pos) {
	if (l == r) return sum[v] - sum[u];
	int mid = (l + r) >> 1;
	if (pos <= mid) return query(ls[u], ls[v], l, mid, pos);
	return query(rs[u], rs[v], mid + 1, r, pos);
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) c[i] = read();
	for (int i = 1; i <= n; ++i) d[i] = read();
	for (int i = 2; i <= n; ++i) {
		fa[i][0] = read();
		Add(fa[i][0], i);
		son[fa[i][0]]++;
	}
	PreWork(1);
	bfs();
	int flag = 1;
	for (int i = 1; i <= n; ++i) {
		if (son[i] > 1) flag = 0;
	}
	if (flag) {
		for (int i = 1; i <= m; ++i) {
			int u = read(), k = read();
			int faa = Find(u, k);
			if (c[u] == c[faa]) cout << d[u] << '\n';
			else cout << 0 << '\n';
		}
		return 0;
	}
	rt[0] = build(1, n);
	for (int i = 1; i <= n; ++i) {
		rt[i] = modify(rt[i - 1], 1, n, c[idex[i]], d[idex[i]]);
	}
	for (int i = 1; i <= m; ++i) {
		int u = read(), k = read();	
		int faa = Find(u, k);
		if (!faa) {
		    cout << 0 << '\n';
		    continue;
		}
		int l = 1, r = bfn[u] - 1, mid;
		int L, R;
		while (l <= r) {
			mid = (l + r) >> 1;
			if (Find(idex[mid], k) == faa) r = mid - 1;
			else l = mid + 1;
		}
		L = l;
		l = bfn[u] + 1, r = n;
		while (l <= r) {
			mid = (l + r) >> 1;
			if (Find(idex[mid], k) == faa) l = mid + 1;
			else r = mid - 1;
		}
		R = l - 1;
		printf("%d\n", query(rt[L - 1], rt[R], 1, n, c[faa]));
	}
	return 0;
}