[Codeforces #316 D. Tree Requests]DFS序、离线、二分

[Codeforces #316 D. Tree Requests]DFS序、离线、二分

1. 题目链接

[Codeforces #316 D. Tree Requests]

2. 题意描述

给定一棵 N 个节点的树, 每个节点对应26个小写字母中的一个字母,节点 i 的深度记为depi M 次询问,每次询问节点u的子树中(含节点 u )的所有深度为depi的节点,分别取得他们对应的字母,构成一个字符串。将这个串按照某个排列,问能不能构成一个回文串。

3. 解题思路

某个字符串的某个排列能够构成回文串,当且仅当,这个字符串中的每种字符出现为奇数次的种数 1 ,所以,可以用bitset<26> 来维护每种字符出现次数的奇偶性。
首先,求出树的dfs序,即求出每个节点进入 lb 和出来的时间编号 ub , 并且求出每个节点的深度。这样就可以用 [lb,ub] 表示以该节点为根节点的子树。
然后,求询问离线,按照不同深度分别处理。
对于同一深度的询问。先处理出,该深度下所有节点的前缀异或和。然后对该深度下所有节点的左边界二分,对右边界再来一次二分。然后两个二分得到的值异或得到该查询的答案。

4. 实现代码

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 500000 + 5;
const int BITS = 26;

int n, m;
char s[MAXN];
struct Edge {
    int v, next;
} edge[MAXN];
int head[MAXN], tot;

struct Node {
    int dep, lb, ub, mxdep;
} node[MAXN];
int nid;

vector<int> H[MAXN];

struct Query {
    int id, u;
};
vector<Query> I[MAXN];
bool Ans[MAXN];

void init() {
    tot = 0; nid = 0;
    memset(head, -1, sizeof(head));
    for(int i = 0; i < MAXN; i++) H[i].clear(), I[i].clear();
}

void add_edge(int u, int v) {
    edge[tot] = Edge{v, head[u]};
    head[u] = tot ++;
}

void dfs(int u, int k) {
    node[u].mxdep = node[u].dep = k;
    node[u].lb = ++ nid;
    H[k].push_back(u);
    for(int i = head[u], v; ~i; i = edge[i].next) {
        v = edge[i].v;
        dfs(v, k + 1);
        node[u].mxdep = max(node[u].mxdep, node[v].mxdep);
    }
    node[u].ub = nid;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
#endif // ONLINE_JUDGE
    scanf("%d %d", &n, &m);

    init();
    for(int u, v = 2; v <= n; v++) {
        scanf("%d", &u);
        add_edge(u, v);
    }

    scanf("%s", s + 1);

    dfs(1, 1);

    for(int i = 0, u, h; i < m; i++) {
        scanf("%d %d", &u, &h);
        I[h].push_back(Query{i, u});
    }
    vector<int> L(MAXN), R(MAXN);
    vector<bitset<BITS> > pre(MAXN);
    for(int h = 0; h < MAXN; h++) {
        int sz = I[h].size(), hsz = H[h].size();
        if(sz == 0) continue;

        pre[0].reset();
        for(int i = 0; i < hsz; i++) {
            int& u = H[h][i];
            L[i] = node[u].lb;
            R[i] = node[u].ub;
            pre[i + 1] = pre[i];
            pre[i + 1].flip(s[u] - 'a');
        }
        for(int i = 0; i < sz; i++) {
            int& id = I[h][i].id, &u = I[h][i].u;
            if(hsz == 0) Ans[id] = true;
            else if(node[u].dep >= h) Ans[id] = true;
            else if(node[u].mxdep < h) Ans[id] = true;
            else {
                int &lb = node[u].lb, &ub = node[u].ub;
                int LB = lower_bound(L.begin(), L.begin() + hsz, lb) - L.begin();
                int UB = upper_bound(R.begin(), R.begin() + hsz, ub) - R.begin() - 1;
                if(LB > UB) {
                    Ans[id] = true;
                    continue;
                }
                bitset<BITS> bs = pre[UB + 1] ^ pre[LB];
                if(bs.count() <= 1) Ans[id] = true;
                else Ans[id] = false;
            }
        }
    }
    for(int i = 0; i < m; i++) {
        puts(Ans[i] ? "Yes" : "No");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值