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;
}