「TJOI 2018」异或

传送门


problem

现在有一颗以 1 1 1 为根节点的由 n n n 个节点组成的树,树上每个节点上都有一个权值 v i v_i vi。现在有 Q Q Q 次操作,操作如下:

  • 1    x    y 1\;x\;y 1xy:查询节点 x x x 的子树中与 y y y 异或结果的最大值;
  • 2    x    y    z 2\;x\;y\;z 2xyz:查询路径 x x x y y y 上点与 z z z 异或结果最大值。

数据范围: 1 < n , Q ≤ 1 0 5 1<n,Q\leq10^5 1<n,Q105,查询 1 1 1 中的 y < 2 30 y<2^{30} y<230,查询 2 2 2 中的 z < 2 30 z<2^{30} z<230


solution

这就是 可持久化 01 01 01 Trie 的板子题啊,只不过是搬到了树上而已,套一个树链剖分即可。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

PS:貌似还有 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的做法,有空学一学。


code

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,q,t;
int val[N],first[N],v[N<<1],nxt[N<<1];
void Max(int &x,int y)  {if(x<y)x=y;}
void add(int x,int y){
	nxt[++t]=first[x],first[x]=t,v[t]=y;
}
namespace Trie{
	int tot,root[N],sze[N<<5],ch[N<<5][2];
	void Insert(int &x,int y,int val){
		x=++tot;
		sze[x]=sze[y]+1;
		int tmp=x;
		for(int i=30;~i;--i){
			int k=(val>>i)&1;
			ch[tmp][k^1]=ch[y][k^1];
			ch[tmp][k]=++tot,y=ch[y][k];
			sze[tmp=tot]=sze[y]+1;
		}
	}
	int Query(int x,int y,int val){
		int ans=0;
		for(int i=30;~i;--i){
			int k=(val>>i)&1;
			if(sze[ch[x][k^1]]<sze[ch[y][k^1]])
				ans|=(1<<i),x=ch[x][k^1],y=ch[y][k^1];
			else  x=ch[x][k],y=ch[y][k];
		}
		return ans;
	}
}
using namespace Trie;
namespace Tree_cutting{
	int tot,fa[N],dep[N],son[N],Size[N],top[N],pos[N],idx[N];
	void dfs1(int x){
		Size[x]=1;
		for(int i=first[x];i;i=nxt[i]){
			int to=v[i];
			if(to==fa[x])  continue;
			fa[to]=x,dep[to]=dep[x]+1,dfs1(to),Size[x]+=Size[to];
			if(Size[to]>Size[son[x]])  son[x]=to;
		}
	}
	void dfs2(int x,int tp){
		top[x]=tp,idx[pos[x]=++tot]=x;
		if(son[x])  dfs2(son[x],tp);
		for(int i=first[x];i;i=nxt[i])
			if(v[i]!=fa[x]&&v[i]!=son[x])  dfs2(v[i],v[i]);
	}
	int ask(int x,int y,int z){
		int ans=0;
		while(top[x]!=top[y]){
			if(dep[top[x]]<dep[top[y]])  swap(x,y);
			Max(ans,Query(root[pos[top[x]]-1],root[pos[x]],z));
			x=fa[top[x]];
		}
		if(dep[x]>dep[y])  swap(x,y);
		return max(ans,Query(root[pos[x]-1],root[pos[y]],z));
	}
}
using namespace Tree_cutting;
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i)  scanf("%d",&val[i]);
	for(int i=1,x,y;i<n;++i){
		scanf("%d%d",&x,&y),add(x,y),add(y,x);
	}
	dfs1(1),dfs2(1,1);
	for(int i=1;i<=n;++i)  Insert(root[i],root[i-1],val[idx[i]]);
	int op,x,y,z;
	while(q--){
		scanf("%d%d%d",&op,&x,&y);
		if(op==1)  printf("%d\n",Query(root[pos[x]-1],root[pos[x]+Size[x]-1],y));
		else  scanf("%d",&z),printf("%d\n",ask(x,y,z));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值