CF570D Tree Requests 题解 树上启发式合并

题目链接: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;
}
posted @ 2020-11-05 11:04  quanjun  阅读(61)  评论(0编辑  收藏  举报