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];
};