CF570D Tree Requests

传送门

CF
luogu(目前无法提交,详见洛谷)
VJudge

题目大意

给定一颗以节点\(1\)为根的树,包含\(n\)个节点,每个节点上都有一个字符\(c_i\)
特别的,\(1\)号节点的深度为\(1\)
共有\(m\)个问询,每次询问:在子树\(v_i\)中所有深度(相对于根)\(h_i\)的所有节点能否构成回文串。

数据范围

  • \(1\leq n,m\leq 5\times10^5\)
  • \(1\leq v_i,h_i\leq 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]数组统计当前子树中深度为\(dep\)的字符\(ch\)出现次数。
离线统计问询中\(v_i=u\)的情况,在\(\text{dfs}\)到子树\(u\)[1]时只需将所有问询为\(u,d_{u,j}\)对应的cnt[d_u,j][0~25]全部&=1再加到\(res\)上,若最终\(res\leq 1\)则说明可以构成回文串。

6 5
1 1 1 3 3
zacccd
1 1
3 3
4 1
6 1
1 2

中,将1 11 2挂到节点1上处理

dsu on tree

由于空间限制,必须重复利用cnt数组,为此,在每颗树内,除了最后一个被枚举到的子树不会影响到后续(根本就没有后续)之外全部都要清空。
那么为什么需要清空子树呢?
虽然某颗子树不清空不会影响原树的统计,但是会影响到原树下的其他子树(在其后枚举的子树)
为了子树之间不影响,必须在dfs完时将其cnt清空。
在统计完最后一颗子树时,就要统计原树了。
那么很显然如果将重子树放在最后处理,清空的量是最少的。
虽然感觉很暴力玄学,但是时间复杂度仍然达到了惊人的\(\text{O(NlogN)}\)
证明看这里
此时再把前面的子树贡献加回来,补全原树。
最后特判根节点贡献就行了。

代码:

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了#TestPoint 2……

输入的时候要警惕#TP41,输入多打了一个回车,那些没有使用以下代码来跳过回车/空格

while (c == '\n || c == ' ') c = getchar();

的人估计就是这个点被HACK了
CF的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;
}

后记

本人第一次写蓝题题解,若有不满,可在评论区提出,我一定尽力改正,谢谢!


  1. 除特殊说明外,文中子树\(u\)皆表示以\(u\)为根节点的子树 ↩︎

posted @ 2025-08-09 14:15  peter_code  阅读(18)  评论(0)    收藏  举报