P3292-[SCOI2016]幸運數位【線性基,LCA,倍增】

2020-10-04 18:00:18

正題

題目連結:https://www.luogu.com.cn/problem/P3292


題目大意

n n n個點的一棵樹,每個點都點權。每次詢問一條路徑,選擇若干個點的互斥或和最大。


解題思路

路徑上的如何進行計算,我們知道我們可以用倍增來計算權值和。我們可以把每個線性基視為邊權,然後加和就是線性基的合併。

合併線性基時我們將後面的所有 d i d_i di都插入到前面那個線性基中即可。

時間複雜度 O ( ( n + q ) l o g 3 n ) O((n+q)log^3 n) O((n+q)log3n)


c o d e code code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=21000,T=15;
struct node{
	ll to,next;
}a[N*2];
struct xxj{
	ll d[80];
	ll solve(){
		ll ans=0;
		for(ll i=60;i>=0;i--)
			if((ans^d[i])>ans)ans^=d[i];
		return ans;
	}
	void add(ll x){
		if(!x)return;
		for(ll i=60;i>=0;i--)
			if((x>>i)&1ll){
				if(d[i])x^=d[i];
				else{
					d[i]=x;
					return;
				}
			}
	}
}w[N][T];
ll n,q,tot,ls[N],dep[N],g[N],f[N][T];
xxj operator+(const xxj &a,const xxj &b){
	xxj ans=a;
	for(ll i=0;i<=60;i++)
		if(b.d[i])ans.add(b.d[i]);
	return ans;
}
void addl(ll x,ll y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;
}
void dfs(ll x){
	dep[x]=dep[f[x][0]]+1;
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==f[x][0])continue;
		f[y][0]=x;dfs(y);
	}
	return;
}
void ycl(){
	for(ll i=1;i<=n;i++)
		w[i][0].add(g[i]);
	for(ll j=1;j<T;j++){
		for(ll i=1;i<=n;i++){
			f[i][j]=f[f[i][j-1]][j-1];
			w[i][j]=w[i][j-1]+w[f[i][j-1]][j-1];
		}
	}
	return;
}
ll LCA(ll x,ll y){
	xxj ans;memset(ans.d,0,sizeof(ans.d));
	if(dep[x]>dep[y])swap(x,y);
	for(ll i=T-1;i>=0;i--)
		if(dep[f[y][i]]>=dep[x])
			ans=ans+w[y][i],y=f[y][i];
	if(x==y){
		ans.add(g[x]);
		return ans.solve();
	}
	for(ll i=T-1;i>=0;i--)
		if(f[x][i]!=f[y][i])
			ans=ans+w[x][i]+w[y][i],
			x=f[x][i],y=f[y][i];
	ans=ans+w[x][0]+w[y][0];
	ans.add(g[f[x][0]]);
	return ans.solve();
}
int main()
{
	scanf("%lld%lld",&n,&q);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&g[i]);
	for(ll i=1;i<n;i++){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		addl(x,y);addl(y,x);
	}
	dfs(1);ycl();
	while(q--){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		printf("%lld\n",LCA(x,y));
	}
}