P8511 [Ynoi Easy Round 2021] TEST_68 题解
题目描述
给定一棵 \(n\) 个节点的有根树,根节点为 \(1\) 号点,点有点权 \(a_i\) 。
对每个点 \(x\) ,求其子树外 \(a_i\oplus a_j\) 的最大值,若找不出两个点,则答案为 \(0\) 。
数据范围
- \(1\le n\le 5\cdot 10^5,0\le a_i\le 10^{18},1\le fa_i\le i-1\) 。
时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
集合的最大异或值是 \(\texttt{trie}\) 上的一个经典问题,在插入每个数之前查询当前数和已经插入的数的最大异或值即可。
观察样例可知对于大量的节点答案是一样的,具体的,假设全局最大异或值为 \(a_x\oplus a_y\) ,那么对于除了 \(x\to y\) 路径上的所有点,答案均为 \(a_x\oplus a_y\) 。
接下来只需处理 \(x,y\) 分别跳到 \(\texttt{lca(x,y)}\) 路径上所有点的答案。
注意到 \(\overline{\texttt{subtree}(fa_u)}\subseteq\overline{\texttt{subtree}(u)}\) ,因此可以把 \(\texttt{lca(x,y)}\) 放宽到根节点然后自顶向下更新。计算 \(u\) 点答案时可以先继承 \(fa_u\) 的答案,然后将 \(fa_u\) 其他子树中的点加入 \(\texttt{trie}\) 即可。
时间复杂度 \(\mathcal O(n\log n)\) 。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+5,maxm=3e7+5;
int n,x,y,z,tot;
int fa[maxn];
int ch[maxm][2],id[maxm];
ll tmp,w[maxn],res[maxn];
vector<int> g[maxn];
void query(int v)
{
int p=0;
for(int i=59;i>=0;i--)
{
int c=w[v]>>i&1;
if(ch[p][c^1]) p=ch[p][c^1];
else p=ch[p][c];
}
if(id[p]&&(w[v]^w[id[p]])>tmp) tmp=w[v]^w[id[p]],x=v,y=id[p];
}
void insert(int v)
{
int p=0;
for(int i=59;i>=0;i--)
{
int c=w[v]>>i&1;
if(!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c];
}
id[p]=v;
}
void insert_tree(int u,int lim)
{
query(u),insert(u);
for(auto v:g[u]) if(v!=lim) insert_tree(v,lim);
}
void work(int x)
{
memset(ch,tot=tmp=0,sizeof(ch));
vector<int> vec;
for(int i=x;i>1;i=fa[i]) vec.push_back(i);
reverse(vec.begin(),vec.end());
for(auto u:vec) insert_tree(fa[u],u),res[u]=tmp;
}
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;i++) scanf("%d",&fa[i]),g[fa[i]].push_back(i);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
for(int i=1;i<=n;i++) query(i),insert(i);
for(int i=2;i<=n;i++) res[i]=tmp;
z=y,work(x),work(z);///调用 work 函数时会覆盖 x 和 y 的值
for(int i=1;i<=n;i++) printf("%lld\n",res[i]);
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/19057579
浙公网安备 33010602011771号