1. 程式人生 > 其它 >CF246E Blood Cousins Return 題解

CF246E Blood Cousins Return 題解

一道線段樹合併的題,照理來說這道題思路不值得我寫題解但是寫法值得。

應當說對於深度問題線段樹合併可以亂殺,對於這道題每個節點維護一棵關於深度的線段樹,每個葉子節點維護一個 set 表示當前這個節點子樹內該深度的名字組成的 set,注意深度是全域性深度,合併時採用啟發式合併。

然後就是寫法問題。

線段樹合併有兩種常見的寫法:

一種是直接動態開點即每次合併時都開新點然後用新點來維護合併的兩個點的資訊,即若當前要將 \(p2\) 合併到 \(p1\) 上那就新開一個點 \(p\) 代替 \(p1\)

該做法優勢是可以線上查詢,不需要提前離線存詢問,但劣勢是空間開銷較大,偶爾會被卡空間 / MLE。

一種是就將 \(p2\)

合併到 \(p1\) 上,不重新動態開點。

該做法優勢是空間開銷較小,但劣勢是必須提前離線所有詢問,對於每一個點,做完這個點的合併後必須即刻回答詢問,否則會因為一個點被多次修改導致答案錯誤。

正常來講這兩種寫法都能過題,但是對於這道題葉子節點的合併複雜度不是 \(O(1)\),需要啟發式合併,所以不能採用第一種做法(第一種做法會 TLE)。

GitHub:CodeBase-of-Plozia

Code:

/*
========= Plozia =========
	Author:Plozia
	Problem:CF246E Blood Cousins Return
	Date:2022/5/8
========= Plozia =========
*/

#include <bits/stdc++.h>
using std::set;
using std::string;
using std::vector;

typedef long long LL;
const int MAXN = 1e5 + 5;
int n, Head[MAXN], cntEdge, fa[MAXN], Root[MAXN], q, dep[MAXN], cntSgT, ans[MAXN];
string Name[MAXN];
vector <int> Query[MAXN], id[MAXN];
bool vis[MAXN];
struct node { int To, Next; } Edge[MAXN << 1];
struct SgT
{
	set <string> s;
	int ls, rs;
	#define ls(p) tree[p].ls
	#define rs(p) tree[p].rs
}tree[MAXN * 20];

int Read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
	return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
void addEdge(int x, int y) { ++cntEdge; Edge[cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; }

void Insert(int &p, int x, string v, int lp, int rp)
{
	if (!p) p = ++cntSgT;
	if (lp == rp) { tree[p].s.insert(v); return ; }
	int mid = (lp + rp) >> 1;
	if (x <= mid) Insert(ls(p), x, v, lp, mid);
	else Insert(rs(p), x, v, mid + 1, rp);
}

void Merge(int &p1, int p2, int lp, int rp)
{
	if (!p1 || !p2) { p1 = p1 + p2; return ; }
	if (lp == rp)
	{
		if (tree[p1].s.size() < tree[p2].s.size()) std::swap(p1, p2);
		for (auto it = tree[p2].s.begin(); it != tree[p2].s.end(); ++it) tree[p1].s.insert(*it);
		return ;
	}
	int mid = (lp + rp) >> 1;
	Merge(ls(p1), ls(p2), lp, mid);
	Merge(rs(p1), rs(p2), mid + 1, rp);
}

int Ask(int p, int x, int lp, int rp)
{
	if (x < lp || x > rp || p == 0) return 0;
	if (lp == rp) return tree[p].s.size();
	int mid = (lp + rp) >> 1;
	if (x <= mid) return Ask(ls(p), x, lp, mid);
	else return Ask(rs(p), x, mid + 1, rp);
}

void dfs(int now, int father)
{
	dep[now] = dep[father] + 1;
	Insert(Root[now], dep[now], Name[now], 1, n);
	for (int i = Head[now]; i; i = Edge[i].Next)
	{
		int u = Edge[i].To; if (u == father) continue ;
		dfs(u, now); Merge(Root[now], Root[u], 1, n);
	}
	for (int i = 0; i < Query[now].size(); ++i) ans[id[now][i]] = Ask(Root[now], dep[now] + Query[now][i], 1, n);
}

int main()
{
	n = Read();
	for (int i = 1; i <= n; ++i)
	{
		std::cin >> Name[i]; fa[i] = Read();
		if (fa[i]) addEdge(i, fa[i]), addEdge(fa[i], i);
	}
	q = Read();
	for (int i = 1; i <= q; ++i) { int v = Read(), k = Read(); Query[v].push_back(k); id[v].push_back(i); }
	for (int i = 1; i <= n; ++i)
		if (fa[i] == 0) dfs(i, i);
	for (int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
	return 0;
}