题目:一棵树上每个节点有个字符值,询问每个节点的深度为h的子节点的字符是否能组成一个回文串。
思路:首先是奇妙的dfs序和时间戳,通过记录每个节点的dfs进出时间,可以发现某个节点的子节点的进出时间均在该节点的进出时间范围内(这是很直观的dfs的性质),这样可以把树形结构转变为线性结构,方便进行各种处理。dfs一遍处理时间戳,每个节点的深度,并记录每个深度的节点都有哪些,此时每个深度的节点就是排好序的。然后对于一个询问,可以利用二分查找确定子节点在该层的哪一段。对于每一层,预先处理每个字符的前缀和,就可以做到O(1)查询区间数量。由于这题只要求奇偶性,故只需要记录01,又因为字符集为26,可以直接用int类型压位。
ps:二分直接用的stl的,结果反而写得像一坨翔一样= =
#include <bits/stdc++.h> #define pb push_back #define se second #define fs first #define sq(x) (x)*(x) #define eps 0.000000001 #define LB lower_bound using namespace std; typedef long long ll; typedef pair<ll,ll> P; const int maxv=5e5+3000; int n,m; vector<int> G[maxv]; vector<int> D[maxv]; vector<int> num[maxv]; int deep[maxv]; int in[maxv],out[maxv]; char s[maxv]; int clo=0; void dfs(int v,int d,int f){ D[d].pb(v); deep[v]=d; in[v]=++clo; for(int i=0;i<G[v].size();i++){ int u=G[v][i]; if(u==f) continue; dfs(u,d+1,v); } out[v]=++clo; } void cul(){ for(int i=1;i<=n;i++){ if(D[i].size()==0){ break; } num[i].pb(1<<(s[D[i][0]-1]-'a')); for(int j=1;j<D[i].size();j++){ int c=s[D[i][j]-1]-'a'; num[i].pb(num[i].back()^(1<<c)); } } } bool cmpl(const int &va,const int &vb){return in[va]<vb;} bool cmpr(const int &va,const int &vb){return out[va]<vb;} int cas=0; int main(){ // freopen("/home/files/CppFiles/in","r",stdin); cin>>n>>m; for(int i=2;i<=n;i++){ int p; scanf("%d",&p); G[p].pb(i); } scanf("%s",s); dfs(1,1,-1); cul(); while(m--){ int v,h; scanf("%d%d",&v,&h); int l=in[v],r=out[v],sd=h; if(deep[v]>h){ puts("Yes"); continue; } vector<int>::iterator itl=LB(D[sd].begin(),D[sd].end(),l,cmpl); vector<int>::iterator itr=LB(D[sd].begin(),D[sd].end(),r,cmpr); if(itr!=D[sd].begin()&&((itr!=D[sd].end()&&out[*itr]>r)||itr==D[sd].end())) itr--; if(itl!=D[sd].end()&&itr!=D[sd].end()&&in[*itl]>=l&&out[*itr]<=r){ int dl=itl-D[sd].begin(); int dr=itr-D[sd].begin(); int nu=(dl>0?num[sd][dl-1]:0); if(nu^num[sd][dr]){ if(__builtin_popcount(nu^num[sd][dr])<2) puts("Yes"); else{ puts("No"); } }else{ puts("Yes"); } }else{ puts("Yes"); continue; } } }