树上启发式合并问题 ---- D. Tree Requests [状态压缩+树上启发式合并]

题目链接


题目大意:

就是给你一颗树,树上每个点都有一个小写字母,现在给你 m ∈ [ 1 , 5 e 5 ] m\in [1,5e5] m[1,5e5]次询问,每次询问给你一个u,和deep,问你在u这课子树中,距离1号节点(即根节点)为deep的全部节点是否可以组合成一个回文字符串,如果可以输出Yes,否则输出No。


解题思路:

1.首先我们知道对于这种题常规讨论就是异或,因为字母只有26个,每个看成一个二进制位,如果所在层所有节点异或和为0或者 2 i 2^i 2i那么这一层就是可以组成一个回文串。

2.首先这道题还要特判两种情况就是deep不够u的深度或者deep超过这个颗子树的最大深度,那么就是空串它也是回文的

3.对于由于是询问子树问题,我们考虑一下树上启发式合并,对于每个深度我们设置一个异或和的数组,我们如何判断这个数是否是 2 i 2^i 2i的数呢,那么就用lowbit,我们知道lowbit返回的最后一位1的位置,如果减了是0,那么就是 2 i 2^i 2i次方。

4.对于每个子树合并答案的时候我们要把询问存下来,去枚举询问,不能枚举深度,那样会TLE.

5.剩下的都是树上启发式合并的常规操作


#include <bits/stdc++.h>

using namespace std;
typedef pair<int,int> PII;
const int maxn = 1e6 + 10;
map<PII,int> ans;
vector<int> G[maxn];
vector<int> q[maxn];
char node[maxn];
int n, m;
int depth[maxn], siz[maxn], son[maxn];
int max_dep[maxn], f[maxn];
PII ask[maxn];
void find_son(int u, int fa) {
	son[u] = 0;
	siz[u] = 1;
	depth[u] = depth[fa] + 1;
	max_dep[u] = depth[u];
	f[u] = fa; 
	for(int i = 0; i < G[u].size(); ++ i) {
		int it = G[u][i];
		if(it == fa) continue;
		find_son(it,u);
		max_dep[u] = max(max_dep[u],max_dep[it]);
		siz[u] = siz[it] + siz[u];
		if(siz[son[u]]  < siz[it])
		   son[u] = it;
	}
}
int flag;
int Xor[maxn];

void dfs1(int u, int fa) {
    Xor[depth[u]] ^= 1ll << (node[u] - 'a');
    for(int i = 0; i < G[u].size(); ++ i) {
        int it = G[u][i];
        if(it == fa || it == flag) continue;
        dfs1(it,u);
    }
}

void dfs(int u, int fa, int keep) {

	for(int i = 0; i < G[u].size(); ++ i) {
		int it = G[u][i];
		if(it == fa || it == son[u]) continue;
		dfs(it,u,0);
	}
	
	if(son[u]) {
		dfs(son[u],u,1);
		flag = son[u];
	}
    
    dfs1(u,fa);

    for(int i = 0; i < q[u].size(); ++ i) {//处理询问
		int it = q[u][i];
        ans[{u,it}] = (Xor[it] - (Xor[it] & -Xor[it]) == 0);
	}
	  

    flag = 0;    
	if(!keep) {
    	for(int i = depth[u]; i <= max_dep[u]; ++ i) Xor[i] = 0;
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	//............................
	cin >> n >> m;
	for(int i = 2; i <= n; ++ i) {
		int x;
		cin >> x;
		G[x].push_back(i);
		G[i].push_back(x);
	}
	cin >> node + 1;
	find_son(1,0);
	for(int i = 1; i <= m; ++ i) {
		int l, r;
		cin >> l >> r;
		ask[i] = {l,r};
		if(depth[l] > r || r > max_dep[l]) ans[{l,r}] = true;//特判
		else q[l].push_back(r);//存储询问
	}
	dfs(1,0,0);
	for(int i = 1; i <= m; ++ i)
	  ans[ask[i]] ? puts("Yes") : puts("No");
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值