1. 程式人生 > 實用技巧 >CF570D Tree Requests 題解 樹上啟發式合併

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