Codeforces 570D Tree Requests(DFS重标号+树状数组)

能成为回文串的显然是数量为奇数的字母小于2个。问题转化成求范围内点中各种字母总个数。


把询问离线处理,按照层数排序,就可以把每一层分开处理了。剩下的问题就是处理子树。

如果能构造出一个序列使同一棵子树的节点都连续在一起。就可以转化为一个区间查询问题。DFS对节点的访问顺序刚好满足这个性质。

在DFS过程中对每个节点重新标号,并对于每个节点,记录进入该节点时的最大标号(就是自己的标号)和出该节点的最大标号。 在区间查询时这两个标号间的所有点都在这个点的子树中。 同时用dv[i]记录第i层的所有点。


用树状数组做单点修改区间查询。每处理完一层,就把这一层的点还原,然后处理下一层。


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#include <set>
#include <cmath>
#define LL long long
#define maxn 500005
#include <vector>

int N,M;
int num[maxn];
int fa[maxn];
int cnt=0;
int hs[maxn];
int sh[maxn];
int l[maxn],r[maxn];
vector <int> G[maxn];
vector <int> dv[maxn];
int idx(char c){ return c-'a'; }
struct node{
    int id,pa,dept;
}Q[maxn];

bool res[maxn];

int c[30][maxn];

bool cmp(node a,node b){
    return a.dept<b.dept;
}

int lowbit(int n) {
    return n&(-n);
}

int sum(int x,int k){
    int res=0;
    while(x){
        res+=c[k][x];
        x-=lowbit(x);
    }
    return res;
}

void update(int x,int v,int k){
    while(x<=N){
        c[k][x]+=v;
        x+=lowbit(x);
    }
}

void dfs(int x,int dep){
    cnt++;
    hs[cnt]=x;
    sh[x]=cnt;
    l[sh[x]]=cnt;
    dv[dep].push_back(cnt);
    for(int i=0;i<G[x].size();i++){
        dfs(G[x][i],dep+1);
    }
    r[sh[x]]=cnt;
}

bool check(int n){
    int nm=0;
    int ql=l[sh[Q[n].pa]],qr=r[sh[Q[n].pa]];
    for(int i=0;i<26;i++){
        int k=sum(qr,i)-sum(ql-1,i);
        if(k&1) nm++;
    }
    if(nm>1) return 0;
    return 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);
    }
    char s[maxn];
    scanf("%s",s);
    for(int i=0;i<N;i++){
        num[i+1]=idx(s[i]);
    }
    for(int i=0;i<M;i++){
        scanf("%d%d",&Q[i].pa,&Q[i].dept);
        Q[i].id=i;
    }
    sort(Q,Q+M,cmp);
    dfs(1,1);
    int d=Q[0].dept;
    for(int i=0;i<dv[d].size();i++){
        int cur=hs[dv[d][i]];
        update(dv[d][i],1,num[cur]);
    }
    res[Q[0].id]=check(0);
    for(int i=1;i<M;i++){
        if(Q[i].dept!=Q[i-1].dept){
            int dp=Q[i-1].dept;
            for(int j=0;j<dv[dp].size();j++){
                int cur=hs[dv[dp][j]];
                update(dv[dp][j],-1,num[cur]);
            }
            dp=Q[i].dept;
            for(int j=0;j<dv[dp].size();j++){
                int cur=hs[dv[dp][j]];
                update(dv[dp][j],1,num[cur]);
            }
        }
        res[Q[i].id]=check(i);
    }
    for(int i=0;i<M;i++){
        if(res[i]) printf("Yes\n");
        else printf("No\n");
    }
}
/*
7 3
1 1 2 2 3 3
abbcddc
1 3
2 3
3 3
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值