题目链接:​​https://codeforces.com/problemset/problem/570/D​

解题思路:

树上启发式合并。

我一开始开了一个 \(cnt[maxn][26]\) 和一个 \(odd[maxn]\),其中:

  • \(cnt[d][c]\) 表示字符 \(c\) 在第 \(d\) 层出现的次数;
  • \(odd[d]\) 表示在第 \(d\) 层出现次数为奇数的字符数。

显然,如果当前节点 \(u\) 对应的子树中的 \(odd[d] \gt 1\),就没有办法构成回文串;如果 \(odd[u] \le 1\),就可以构成回文串。

后来看了一下题解,发现只需要开一个 \(cnt[d]\) ,然后每次碰到一个颜色 \(c\),\(cnt[d]\) 就疑惑上 \(2^c\),这样就可以直接通过判断 \(cnt[d]\) 的二进制表示中 \(1\) 的位数来判断回文了。

示例代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500050;
int n, m, sz[maxn], c[maxn], dep[maxn], cnt[maxn];
bool big[maxn];
vector<int> g[maxn];
char s[maxn];
struct Query {
int d;
bool ans;
} query[maxn];
vector<int> qid[maxn];

void getsz(int u, int d) {
sz[u] ++;
dep[u] = d;
for (auto v: g[u])
getsz(v, d+1), sz[u] += sz[v];
}

void add(int u, int x) {
int d = dep[u];
cnt[d] ^= (1<<c[u]);
for (auto v: g[u])
if (!big[v])
add(v, x);
}
void dfs(int u, bool keep) {
int mx = -1, bigSon = -1; // mx表示重儿子的sz, bigSon表示重儿子编号
for (auto v: g[u])
if (sz[v] > mx)
mx = sz[ bigSon = v ];
for (auto v: g[u])
if (v != bigSon)
dfs(v, false);
if (bigSon != -1)
dfs(bigSon, true),
big[bigSon] = true;
add(u, 1);
// to do
for (auto id: qid[u])
query[id].ans = (__builtin_popcount(cnt[query[id].d]) <= 1);
if (bigSon != -1)
big[bigSon] = false;
if (!keep)
add(u, -1);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; i ++) {
int p;
scanf("%d", &p);
g[p].push_back(i);
}
scanf("%s", s+1);
for (int i = 1; i <= n; i ++) c[i] = s[i] - 'a';
for (int i = 0; i < m; i ++) {
int u;
scanf("%d%d", &u, &query[i].d);
qid[u].push_back(i);
}
getsz(1, 1);
dfs(1, false);
for (int i = 0; i < m; i ++) {
puts(query[i].ans ? "Yes" : "No");
}
return 0;
}