Tree Requests CodeForces - 570D
题意:
树上每个点都有一个小写字符,每次询问以点 \(v\) 为根的子树中深度为 \(h\) 的点的字符是否可以组成一个回文串。
数据范围:\(1 ≤ n, m ≤ 500,000\)
解法 \(1\ (dsu\ on\ tree)\):
不带修改的树上查询问题,转化为树上启发式合并的离线做法。每个字符赋予一个二进制位,维护同一深度的点的字符的异或和,符合要求的异或和为 \(0\) 或者 \(2^x\)。当把一个点的子树信息处理出后,遍历和这个点有关的查询,求出答案,保存下来。
用时:\(1154\ ms\)
代码:
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef pair<int, int> P;
const int N = 5e5 + 5;
int sz[N], son[N], depth[N], ans[N], th[N];
vector<int>G[N];
vector<P>ask[N];
char sc[N];
void dfs1(int v, int d)
{
son[v] = 0;
sz[v] = 1;
depth[v] = d;
for (int i = 0;i < G[v].size();i++)
{
int u = G[v][i];
dfs1(u, d + 1);
sz[v] += sz[u];
if (sz[u] > sz[son[v]])
son[v] = u;
}
}
void add(int v)
{
th[depth[v]] ^= (1 << (sc[v] - 'a'));
for (int i = 0;i < G[v].size();i++)
add(G[v][i]);
}
void del(int v)
{
th[depth[v]] = 0;
for (int i = 0;i < G[v].size();i++)
del(G[v][i]);
}
void dfs2(int v, bool f)
{
for (int i = 0;i < G[v].size();i++)
{
int u = G[v][i];
if (u == son[v])
continue;
dfs2(u, false);
}
if (son[v])
dfs2(son[v], true);
for (int i = 0;i < G[v].size();i++)
{
int u = G[v][i];
if (u == son[v])
continue;
add(u);
}
th[depth[v]] ^= (1 << (sc[v] - 'a'));
for (int i = 0;i < ask[v].size();i++)//和该点有关的查询
{
P t = ask[v][i];
if (th[t.first] == 0||(th[t.first] & (th[t.first] - 1)) == 0)
ans[t.second] = 1;
}
if (!f)
del(v);
}
int main()
{
int n, m, v, h,fa;
scanf("%d%d", &n, &m);
for (int i = 2;i <= n;i++)
{
scanf("%d", &fa);
G[fa].pb(i);
}
scanf("%s", sc + 1);
for (int i = 1;i <= m;i++)
{
scanf("%d%d", &v, &h);
ask[v].pb(make_pair(h, i));
}
dfs1(1, 1);
dfs2(1, false);
for (int i = 1;i <= m;i++)
{
if (ans[i] == 1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
解法 \(2\ (dfs序+二分)\):
利用同一棵子树中的点的 \(dfs\) 序的特点:把同一深度的点放在按 \(dfs\) 得到的顺序放在一起,那么处于同一棵子树的点一定连在一起。
求出树的 \(dfs\) 序,遍历所有的深度,处理有关的查询。对于当前查询的深度 \(h\),作为根的点为 \(v\),只要找到深度为 \(h\) 的点中那一块属于节点 \(v\) 子树的点即可。利用 \(dfs\) 序,我们可以知道 \(dfs\) 序中v的子树的起始 \(dfs\) 序和结尾的 \(dfs\) 序。利用二分就可以求出所要求的那一块点。同时利用前缀和,维护每个深度下,点的字符的前缀和,符合要求的情况为奇数个数的点的数量 \(\leq 1\)。
也是离线处理,用时:\(670\ ms\)。
代码:
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef pair<int,int>P;
const int N=5e5+5;
typedef long long ll;
int sz[N],depth[N],dfn[N],sum[N][28],a[N],ans[N];
vector<int>G[N],th[N];
vector<P>ask[N];
char sc[N];
void dfs(int v,int d,int &cnt)
{
sz[v]=1;
depth[v]=d;
dfn[v]=++cnt;
th[d].pb(v);//相同深度的点
for(int i=0;i<G[v].size();i++)
{
int u=G[v][i];
dfs(u,d+1,cnt);
sz[v]+=sz[u];
}
}
void solve(int n)
{
for(int i=1;i<=n;i++)//枚举深度
{
if(ask[i].size()==0)//没有该深度的查询
continue;
if(th[i].size()==0)//当前深度无点
{
for(int j=0;j<ask[i].size();j++)
ans[ask[i][j].second]=1;
continue;
}
for(int k=0;k<26;k++)
sum[0][k]=(sc[th[i][0]]-'a')==k?1:0;
a[0]=dfn[th[i][0]];
for(int j=1;j<th[i].size();j++)//深度对应的点组成的数组求前缀和
{
int t=th[i][j];
for(int k=0;k<26;k++)
sum[j][k]=sum[j-1][k]+((sc[t]-'a')==k);
a[j]=dfn[t];
}
for(int j=0;j<ask[i].size();j++)
{
P t=ask[i][j];
int l=lower_bound(a,a+th[i].size(),dfn[t.first])-a;
int r=upper_bound(a,a+th[i].size(),dfn[t.first]+sz[t.first]-1)-a-1;
int cot=0;//cout<<"l="<<l<<" r="<<r<<endl;
for(int k=0;k<26;k++)
{
if(l>r||r<0||l<0)
break;
if(l<=0)
{
if(sum[r][k]&1)
cot++;
}
else
{
if((sum[r][k]-sum[l-1][k])&1)
cot++;
}
}
if(cot<=1)
ans[t.second]=1;
}
}
}
int main()
{
int n,m,p,v,h;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)
{
scanf("%d",&p);
G[p].pb(i);
}
scanf("%s",sc+1);
int cnt=0;
dfs(1,1,cnt);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&v,&h);
ask[h].pb(make_pair(v,i));//每个深度对应的查询
}
solve(n);
for(int i=1;i<=m;i++)
{
if(ans[i]==1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}