题目大意
有一个n个节点的有根树,边权为1,点权给定,第i个点的点权是ai。q次询问,每次给定u,v,并保证u是v的祖先。问对于u到v路径上的所有i(包括u,v),最大的ai xor dist(i,v)。其中dist(i,v)是i到v的距离
n≤50000 0≤ai≤n q≤150000
分析
这题的做法很有趣。
我们把v到u的路径每256个节点分成一段(最后一段可能不够256个,可以单独枚举更新答案),容易发现,对于同一段,dist在2进制下从低到高第8位以后都是相同的,最低8位则是从0开始到255。
那么可以预处理每个点向上256个节点对于2进制下高位的trie,并以此维护只考虑最低8位时点权异或对应距离的最大值。例如枚举到点i,它上面一个节点为j,那么把它扔到trie之后插入的值是(dep[i]-dep[j]^a[j])&255
查询时也是一样,每跳256个节点就在trie上贪心走到高位异或最大的节点,然后低位直接由上面插入的最大值得到。
由于每次都跳大约
n√
次,应该预处理答案来提高运行效率
时间复杂度 O(nn√logn√+qn√)
#include <cstdio>
#include <cstring>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N=5e4+5,st=256;
typedef long long LL;
int n,q,fa[N],fa256[N],h[N],tot,e[N<<1],nxt[N<<1],mx[N][st],cnt[N][st],Ans[N][st],ans,val[N],dep[N];
char c;
int read()
{
int x=0,sig=1;
for (c=getchar();c<'0' || c>'9';c=getchar()) if (c=='-') sig=-1;
for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
return x*sig;
}
void Add(int x,int y)
{
e[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}
void dfs(int x)
{
dep[x]=dep[fa[x]]+1;
int i;
if (dep[x]>=st)
{
int l,r,mid,s;
for (i=x;dep[x]-dep[i]<st;i=fa[i])
{
mx[x][val[i]>>8]=max(mx[x][val[i]>>8],(dep[x]-dep[i]^val[i])&255);
cnt[x][val[i]>>8]++;
}
fa256[x]=i;
for (i=1;i<st;i++) cnt[x][i]+=cnt[x][i-1];
for (i=0;i<=(n-1)/st;i++)
{
for (l=s=0,r=st,mid=r>>1;l<r-1;mid=l+r>>1)
{
if ((s^i)<mid)
{
if (cnt[x][r-1]>cnt[x][mid-1]) s|=r-mid,l=mid;else r=mid;
}else
{
if (cnt[x][mid-1]>((l)?cnt[x][l-1]:0)) s|=r-mid,r=mid;else l=mid;
}
}
Ans[x][i]=(s<<8)|mx[x][l];
}
}
for (i=h[x];i;i=nxt[i]) if (e[i]!=fa[x])
{
fa[e[i]]=x; dfs(e[i]);
}
}
int main()
{
n=read(); q=read();
for (int i=1;i<=n;i++) val[i]=read();
for (int x,y,i=1;i<n;i++)
{
x=read(); y=read(); Add(x,y); Add(y,x);
}
for (dfs(1);q--;)
{
int u=read(),v=read(),dis;
for (dis=ans=0;dep[v]-dep[u]>=st;v=fa256[v],dis++) ans=max(ans,Ans[v][dis]);
for (dis<<=8;v!=fa[u];v=fa[v],dis++) ans=max(ans,dis^val[v]);
printf("%d\n",ans);
}
return 0;
}