Luogu P3292
-
1 min read
背景
学长讲线性基时挂了几道题,这道题原本写不出来,看了题解后瞎写了一下,又对着原代码调了调,竟然过了。
打算算一下时间复杂度,分析解法和代码的实现细节。
题意
给一棵树,每个节点i上有一个数字$G_{i}$,q个询问,寻找x-y简单路径上数字的最大异或值。
$M=log(G_{i}) \leq 60$
分析
由最大异或值可以想到线性基,其中插入、求异或最大值的操作都需log(x)复杂度。
x-y简单路径让我想到倍增LCA
void insert(int *a,int x)
{
for(int i=M;i>=0;--i)
{
if((x >> i)&1)
{
if(!a[i])
{
a[i]=x;
break;
}
else x^=a[i];
}
}
}
int querymax()
{
int ret=0;
for(int i=M;i>=0;--i)
{
ret=max(ret*1,ret^ans[i]);
}
return ret;
}
对q个询问,用倍增预处理。
简单定义线性基的merge操作,$O(M^{2})$
void merge(int *a,int *b)//a <-b
{
for(int i=0;i<=M;++i)
{
if(b[i])
{
insert(a,b[i]);
}
}
}
线性基的倍增
用三维数组p表示线性基,p[i][j]包含i到f[i][j]的线性基(左闭右开)。
对深度为x的结点i,p[i][j]仅在(0 <=j <= log(x))的范围内有意义。
insert(p[i][0],a[i]);//数据读入时
for(int i=1;(1 << i)<=dep[now];++i)//倍增中
{
f[now][i]=f[f[now][i-1]][i-1];
memcpy(p[now][i],p[now][i-1],sizeof(p[now][i-1]));
merge(p[now][i],p[f[now][i-1]][i-1]);//将后一个插入前一个
}
暴改LCA
在原求LCA的代码上适当修改,在倍增途中合并线性基。
function <int (int,int)> lca=[&](int x,int y)
{
if(dep[x]>dep[y]) swap(x,y);
int k=dep[y]-dep[x];
for(int i=19;i>=0;--i)
{
if(dep[y]-(1 << i)>=dep[x])
{
merge(ans,p[y][i]);
y=f[y][i];
}
}
if(x==y)
{
merge(ans,p[x][0]);//最后一点
return x;
}
for(int i=19;i>=0;--i)
{
if(f[x][i]==f[y][i]) continue;
merge(ans,p[x][i]);
merge(ans,p[y][i]);
x=f[x][i],y=f[y][i];
}
merge(ans,p[x][0]);//最后的x,y两点与LCA
merge(ans,p[y][0]);
merge(ans,p[f[y][0]][0]);//保证p不超定义域
return f[x][0];
};