HDU 6191 2017广西邀请赛Query on A Tree:可持久化01字典树(区间抑或最大值查询)

本文介绍了一种使用DFS序结合可持久化01字典树解决特定树形DP问题的方法。该方法适用于求解以某个节点为根的子树中所有节点权值与给定值进行按位异或操作后的最大值问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出一棵n(<=1e5)个点的树,每个点有一个权,询问q(<=qe5)次,每次询问(nod,val),计算出以nod为根的子树上的所有点,权抑或val的最大值是多少。

题解:基本上是个板子题吧。直接讲方法了。。直接上DFS序+可持久化01字典树就行了。可持久化01字典树可以用32步查得一个区间内某个数字和val抑或得到的最大答案,那么DFS序一下就可以把子树搞成区间。然后完美解决。


可持久化01字典树:可以理解成在字典树的每个节点上弄了个前缀和一样的东西吧,重点在空间复用。每个字典树节点增加sum域,记录前边数字中有多少个数字走到这个节点。每次加入一个新的数字,都开一个“全新”的字典树,把每个树的根都记下来。这个新的字典树包括了之前加入的所有数字,也包括当前数字,但是这样超级浪费空间,怎么办呢,我们知道 加入一个新的数字,和上一棵树最多只会有32个点不同。那么新树和上一个树不变的点就直接用上一棵树的就行了(两个树相同的部分重合起来,不需要新开点来存了),把新的点的sum给更新了就行了。


查询:查询区间[ l , r ]时,同时查root[ l-1 ] 和 root[ r ]这两棵树,同时往下走,如果sum不同, 说明[ l , r ]区间内的某个数字出现在这条链路上,那么就可以往这个方向走。然后取抑或最大操作和普通01字典树无异,都是贪心的高位能取不同就不同,否则取相同(相同和不同必然可以取一个,要不然岂不是说明这个区间内没有数字了。。。)。


Flag:01字典树什么的不可能写WA的hhhhhh(除非是能把x写成y的lowB葫芦娃)


以及博主最近沉迷冒险岛2。。。。。。没救了

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e5+100;
int bas[35];
int nxt[MAX<<5][2];
int root[MAX];
int sum[MAX<<5];
int n,q;
vector<int>E[MAX];
int st[MAX],en[MAX],rk[MAX];
int a[MAX];
int cnt;
int tot;
void sheet(){
	bas[0]=1;
	for (int i=1;i<=30;i++){
		bas[i] = bas[i-1]<<1;
	}
}
void init(){
	for (int i=0;i<=n;i++){
		E[i].clear();
	}
	cnt =tot=0;
	memset(nxt[0],0,sizeof nxt[0]);
}
void input(){
	for (int i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	for (int u=2;u<=n;u++){
		int v;
		scanf("%d",&v);
		E[u].push_back(v);
		E[v].push_back(u);
	}
}
void dfs(int node ,int  father ){
	st[node] = ++tot;
	rk[tot] = node;
	for (int des:E[node]){
		if(des==father){
			continue;
		}
		dfs(des,node);
	}
	en[node] = tot;
}
int create(){
	cnt++;
	memset(nxt[cnt],0,sizeof nxt[cnt]);
	return cnt;
}
int insert(int rt,int val){
	int y = ++cnt;
	int x = rt;
	int res = y;
	for (int i=30;i>=0;i--){
		sum[y] = sum[x]+1;
		nxt[y][0] = nxt[x][0];
		nxt[y][1] = nxt[x][1];
		int t = val&bas[i];
		t>>=i;
		nxt[y][t] = create();
		y = nxt[y][t];
		x = nxt[x][t];
	}
	sum[y] = sum[x]+1;
	return res;
}
int query(int l,int r,int val){
	int res =0;
	int x = l;
	int y = r;
	for (int i=30;i>=0;i--){
		int t = val&bas[i];
		t>>=i;
		if (sum[nxt[y][!t]]-sum[nxt[x][!t]]){
			y = nxt[y][!t];
			x = nxt[x][!t];
			res+=bas[i];
		}else{
			y = nxt[y][t];
			x = nxt[x][t];
		} 
	}
	return res;
}
void solve(){
	dfs(1,0);
	for (int i=1;i<=n;i++){
		root[i] = insert(root[i-1],a[rk[i]]);
	}
	while (q--){
		int nod,x;
		scanf("%d%d",&nod,&x);
		printf("%d\n",query(root[st[nod]-1],root[en[nod]],x));
	}
}
int main(){
	sheet();
	while (scanf("%d%d",&n,&q)!=EOF){
		init();
		input();
		solve();
	} 
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值