P4592 TJOI2018 异或
题目
思路
这题看了好久,一直在不理解构建树链剖分的作用,以及构建可持续字典树的作用卡壳.
为什么要构建树链剖分:
寻找子树,简单路径,以及需要我们寻找树上的每个节点权值
可持续字典树:
在我们树链剖分时,我们会按照根节点,重儿子节点,轻儿子节点顺序遍历,那么,我们在添加节点的时候,我们也优先按照根节点,重儿子节点,轻儿子节点的顺序添加节点.我们可以在树剖时候,可以将每次添加节点的顺序记录出来,比如添加的节点为2,这是我们添加的第i个,我们就定义一个数组a[i]=2,再添加一个数组b,可以用来记录节点2对应的i值.之后,在插入构建root时就按照添加的顺序来
我们接着在分析问题:
- 1 x z:查询节点 x 的子树中的节点权值与 z 异或结果的最大值。
- 2 x y z:查询节点 x 到节点 y 的简单路径上的节点的权值与 z 异或结果最大值。
根据我们构建的可持续化字典树
问题1:我们就可以通过将x节点转化成对应的b,
由于我们是根据根节点的顺序进行构建线段树,所以x最后一个子节点对应的添加次数就是b+size[x],因此问题1,就可以通过对root[b+size[x]]与root[b-1]的查询解决问题
问题2:
操作1: 我们先考虑两个节点x,y(y深度保持大于x)在同一条重链上的情况,此时重链就为简单路径,此时我们可以直接通过
root[bx-1]与root[by]
操作2:如果不在一条链上,我们也先考虑深度大的,对深度大的操作,我们也可以先对y节点对应的重链头部进行操作1(该重链操作完毕),之后,在将y节点转化成重链头部的父节点(上一条重链),再次进行操作2直到两个节点都在同一条重链上
![![[Pasted image 20251029123428.png]]](https://i-blog.csdnimg.cn/direct/b9df2f7d2ec84c299d198a9d4b5026e5.png)
我们假设 1,2,3…为对应的节点
1,2,4为一条重链
3为一条重链
5,7为一条重链
6为一条重链
我们现在查找3,6之间最短路径节点权值与x的异或最小值
这里,我们先将3自己的权值与x进行异或,接着,再将6对应的权重与x进行异或,然后,再将1,2,4这条重链上的2,1节点权重与x分别进行异或,之后将5,7这条重链上的5的权重与x进行异或,现在我们发现此时x,y对应的值都为1,跳出循环,直接将1的权重与x进行异或
注:这里的异或说的比较简单,实际上是需要通过可持续化字典树通过上述的操作1进行查阅.
代码
#include<iostream>#include<string>#include<algorithm>#include<vector>using namespace std;const int N=1e5+10;int n,m;int a[N];vector<int>vec[N];int cnt;int id[N];//节点对应的cntint son[N];int f[N];int dep[N];int sz[N];int top[N];int val[N];void dfs(int u,int fa){sz[u]=1;f[u]=fa;dep[u]=dep[fa]+1;for(int i=0;i<vec[u].size();i++){int v=vec[u][i];if(v==fa)continue;dfs(v,u);sz[u]+=sz[v];if(sz[son[u]]<sz[v])son[u]=v;}}void dfs2(int u,int t){top[u]=t;id[u]=++cnt;val[cnt]=a[u];
//一定不要忘记返回这个条件if(!son[u])return;dfs2(son[u],t);for(int i=0;i<vec[u].size();i++){int v=vec[u][i];if(v==son[u])continue;if(v==f[u])continue;dfs2(v,v);}}int tot=0;int size[N*35];int root[N*35];int ch[N*35][2];void insert(int u,int v,int p){for(int i=29;i>=0;i--){int w=(p>>i&1);ch[u][!w]=ch[v][!w];ch[u][w]=++tot;size[ch[u][w]]=size[ch[v][w]]+1;u=ch[u][w];v=ch[v][w];} }int query(int u,int v,int p){int res=0;for(int i=29;i>=0;i--){int w=(p>>i&1);if(size[ch[u][!w]]>size[ch[v][!w]]){res+=(1<<i);u=ch[u][!w];v=ch[v][!w];}else{u=ch[u][w];v=ch[v][w];}}return res; }int query_range(int a,int b,int p){int res=0;int fa=top[a];int fb=top[b];while(fa!=fb){if(dep[fa]<dep[fb]){swap(fa,fb);swap(a,b);}
//这里,祖节点建立的早,所以是祖节点线段树的上一个,即root[id[fa]-1],我在这里最开始把祖节点和子节点建立顺序弄反了,调了好长时间res=max(res,query(root[id[a]],root[id[fa]-1],p));a=f[fa];fa=top[a];}if(dep[a]<dep[b])swap(a,b);res=max(res,query(root[id[a]],root[id[b]-1],p));return res;}int main(void){cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n-1;i++){int a,b;cin>>a>>b;vec[a].push_back({b});vec[b].push_back({a});}dfs(1,1);dfs2(1,1);for(register int i = 1 ; i <= n ; i ++) root[i] = ++ tot , insert(root[i] ,root[i - 1],val[i]) ;for(int i=1;i<=m;i++){int opt;cin>>opt;if(opt==1){int a,b;cin>>a>>b;cout<<query(root[id[a]+sz[a]-1],root[id[a]-1],b)<<endl;}else{int a,b,k;cin>>a>>b>>k;cout<<query_range(a,b,k)<<endl;}}}
