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;
}

参考博客

posted @ 2020-04-08 22:29  xzx9  阅读(148)  评论(0编辑  收藏  举报