传送门
题目大意
给定一颗以节点
1
1
1为根的树,包含
n
n
n个节点,每个节点上都有一个字符
c
i
c_i
ci。
特别的,
1
1
1号节点的深度为
1
1
1。
共有
m
m
m个问询,每次询问:在子树
v
i
v_i
vi中所有深度(相对于根)
h
i
h_i
hi的所有节点能否构成回文串。
数据范围
- 1 ≤ n , m ≤ 5 × 1 0 5 1\leq n,m\leq 5\times10^5 1≤n,m≤5×105
- 1 ≤ v i , h i ≤ n 1\leq v_i,h_i\leq n 1≤vi,hi≤n
样例
输入输出样例 #1
输入 #1
6 5
1 1 1 3 3
zacccd
1 1
3 3
4 1
6 1
1 2
输出 #1
Yes
No
Yes
Yes
Yes
思路
dsu on tree 板子题。
首先分析构成回文串的要求,可以用一个cnt[dep][ch]数组统计当前子树中深度为
d
e
p
dep
dep的字符
c
h
ch
ch出现次数。
离线统计问询中
v
i
=
u
v_i=u
vi=u的情况,在
dfs
\text{dfs}
dfs到子树
u
u
u1时只需将所有问询为
u
,
d
u
,
j
u,d_{u,j}
u,du,j对应的cnt[d_u,j][0~25]全部&=1再加到
r
e
s
res
res上,若最终
r
e
s
≤
1
res\leq 1
res≤1则说明可以构成回文串。
例
6 5
1 1 1 3 3
zacccd
1 1
3 3
4 1
6 1
1 2
中,将1 1和1 2挂到节点1上处理
dsu on tree
由于空间限制,必须重复利用cnt数组,为此,在每颗树内,必须将其重儿子放在最后处理(dsu on tree),这样重儿子可以直接继承到原树,因为最后一个被枚举到的子树不会影响到后续,不用清空。
为什么需要清空子树呢?
虽然某颗不清空不会影响原树的统计,但是会影响到原树下的其他子树(在其后枚举的子树)
为了子树之间不影响,必须在dfs完清儿子后将其cnt清空。
在统计完最后一颗子树时,就要统计原树了。
此时再把前面的子树贡献加回来,补全原树。
最后特判根节点贡献就行了。
代码:
struct QUERY
{
int id; // 此问询在问询列表中的位置
int h; // 此问询的查询深度
};
vector<QUERY> query[N]; // query[u]: 节点u的问询
vector<int> e[N]; // 原题输入给的是父亲表示法,此处转为儿子表示法
void merge(int u)
{
++cnt[dep[u]][a[u]];
for (auto j: e[u]) merge(j);
}
void delet(int u)
{
--cnt[dep[u][a[u]]; // 或者直接等于0
for (auto j: e[u]) delet(j);
}
// dfs1是树剖,用于确定重儿子和深度
dfs2(int u, bool ishvy) // 当前枚举到子树u,及是否为重儿子
{
for (auto j: e[u]) { // 枚举u的儿子
if (j == hson[u]) continue;
dfs2(j, 0);
}
// 注意判断u是否为叶子节点,没有重儿子
if (hson[u]) dfs2(hson[u], 1);
for (auto j: e[u]) {
if (j == hson[u]) continue;
merge(j);
}
++cnt[dep[u]][a[u]];
int check = 0;
for (auto i: query[u]) {
for (int j = 0; j < 26; ++j) check += cnt[i.h][j] & 1;
if (check <= 1) ans = 1;
}
if (!ishvy) delet(u);
}
坑
写cnt的时候千万不要将cnt[i][j]错当成子树i了,我灵(脑)光(子)乍(抽)现(了)搞错了结果过了样例然后WA了TP2……
输入的时候要警惕#TP41,输入多打了一个回车,那些没有用
while (c == '\n || c == ' ') c = getchar();
的人估计就是这个点被HACK了
CF的hack机制还是太难绷了,居然有人用这种数据hack别人
完整代码
/*
AUTHOR: peter_code
DATE: 25/08/08
PROBLEM: CF570D
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, m, a[N]; // 节点数、问询数、节点权
vector<int> e[N]; // 节点i所有的儿子
int hson[N]; // 重儿子
int sz[N]; // 子树大小
int dep[N]; // 节点深度
bool ans[N]; // 记录"Yes"/"No"结果
int cnt[N][26]; // cnt[i][ch]: 深度为i,权为ch的节点出现次数
// 节点u上的问询维护:
struct QUERY
{
int id; // 第 id 个问询
int h; // 查询树中深度为h[i]的回文串构成情况
};
vector<QUERY> query[N];
int dfs1(int u, int d)
{
hson[u] = 0;
sz[hson[u]] = 0;
dep[u] = d;
sz[u] = 1;
for (auto i: e[u]) {
sz[u] += dfs1(i, d + 1);
if (sz[i] > sz[hson[u]]) hson[u] = i;
}
return sz[u];
}
void merge(int u) // 合并根为u的子树
{
++cnt[dep[u]][a[u]];
for (auto v: e[u]) merge(v);
}
void delet(int u) // 消除子树u的贡献
{
cnt[dep[u]][a[u]] = 0;
for (auto v: e[u]) delet(v);
}
void dfs2(int u, bool hvy) // 节点x,是否为重儿子(是否需保留信息)
{
for (auto v: e[u]) {
if (v == hson[u]) continue; // 优先处理轻儿子,重儿子最后处理
dfs2(v, 0);
}
// 统计u重儿子的贡献(不会被清)
if (hson[u]) dfs2(hson[u], 1); // 注意u为叶子节点无重儿子情况
for (auto v: e[u]) {
if (v == hson[u]) continue; // 重儿子已经统计过了
merge(v); // 统计轻儿子贡献
}
++cnt[dep[u]][a[u]]; // 特判根节点贡献
// 统计挂在节点u上的答案
//printf("In Sub-tree %d:\n", u);
for (auto i: query[u]) {
int res = 0;
for (int j = 0; j < 26; ++j) {
//printf("\tThere are %d '%c' on depth %d.\n", cnt[u][i.h]);
res += cnt[i.h][j] & 1;
}
ans[i.id] = res <= 1;
}
if (!hvy) delet(u);
}
int main()
{
//freopen("D.in", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; ++i) {
int tmp;
scanf("%d", &tmp);
e[tmp].push_back(i);
}
char c = getchar();
while (c == '\n' || c == ' ') c = getchar();
for (int i = 1; i <= n; ++i) {
a[i] = c - 'a';
c = getchar();
}
for (int i = 1; i <= m; ++i) {
int tmpx, tmpy;
scanf("%d%d", &tmpx, &tmpy);
query[tmpx].push_back({i, tmpy});
}
dfs1(1, 1);
dfs2(1, 1);
for (int i = 1; i <= m; ++i) {
if (ans[i]) printf("Yes\n");
else printf("No\n");
}
return 0;
}
后记
本人第一次写蓝题题解,若有不满,可在评论区提出,我一定尽力改正,谢谢!
除特殊说明外,文中子树 u u u皆表示以 u u u为根节点的子树 ↩︎
1万+



被折叠的 条评论
为什么被折叠?



