题目:

给一棵树,每个点有一个英文字母。次询问,问子树内所有深度为的点里的字母能否重排成一个回文串。


做法:

处理子树上的询问,不带修改,询问可离线,我们可以考虑用。这是一种统计子树信息的方式,通过调整顺序,保留重儿子中的信息来优化复杂度。
在这个题里,我们要做的就是对每棵子树,得到数组,代表这棵子树内深度为的点中,字符出现的次数。因为能否重排成回文串我们只需知道奇数字符的数量,设为,若>1则,否则。所以这题其实是很裸的,我们每次处理出子树的数组,然后就能判断完该子树下的所有询问。


代码:

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define debug(a) cout << #a ": " << a << endl
using namespace std;
typedef long long ll;
const int N = 5e5 + 7;
char s[N];
vector<int> g[N];
vector<pair<int,int>> ask[N];
int dep[N], sze[N], son[N], skip;
int cnt[N][26], ans[N];
void dfs(int u){
    sze[u] = 1, son[u] = 0;
    for (auto &v : g[u]){
        dep[v] = dep[u]+1;
        dfs(v);
        sze[u] += sze[v];
        if (sze[son[u]] < sze[v]) son[u] = v;
    }
}
void cal(int u, int op){
    cnt[dep[u]][s[u]-'a'] += op;
    for (auto &v : g[u]) if (v != skip){
        cal(v, op);
    }
}
void dsu(int u, int keep){
    for (auto &v : g[u]) if (v != son[u]) dsu(v, 0);
    if (son[u]) dsu(son[u], 1), skip = son[u];
    cal(u, 1); skip = 0;
    for (auto &pr : ask[u]){
        int d = pr.first, id = pr.second;
        int even = 0, odd = 0;
        for (int i = 0; i < 26; ++i){
            (cnt[d][i]&1) ? odd++ : even++;
        }
        ans[id] = (odd > 1) ? 0 : 1;
    }
    if (!keep) cal(u, -1);
}
int main(void){
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 2; i <= n; ++i){
        int x; scanf("%d", &x);
        g[x].push_back(i);
    }
    scanf("%s", s+1);
    for (int i = 1; i <= m; ++i){
        int u, d; scanf("%d%d", &u, &d);
        ask[u].push_back(make_pair(d, i));
    }
    dep[1] = 1;
    dfs(1);
    dsu(1, 0);
    for (int i = 1; i <= m; ++i) printf("%s\n", ans[i] ? "Yes" : "No");
    return 0;
}