[SCOI2016]幸运数字-题解

题目大意见原题面。

其实这个题的思路非常简单,我们知道一些数字的最大异或和可以用线性基来在 O ( l o g   v a l m a x ) O(log\ val_{max}) O(log valmax)时间内快速求取,而线性基的合并也才 O ( l o g 2   v a l m a x ) O(log^2\ val_{max}) O(log2 valmax),所以我们可以用个数据结构维护线性基,然后每次查询就将那条链上的线性基拿出来求取答案即可。

  1. 用线段树+树链剖分来,虽然支持修改,但这个方法不好写,而且复杂度还多一个 l o g n logn logn
  2. 由于没有修改所以直接预处理倍增数组和倍增的线性基,然后每次跳一遍合并出线性基即可。

第二种方法只有 O ( ( n + q ) l o g n ( 6 0 2 ) ) O((n+q)logn(60^2)) O((n+q)logn(602)),而第一种则是 O ( n + q ) l o g 2 n ( 6 0 2 ) ) O(n+q)log^2n(60^2)) O(n+q)log2n(602)),所以直接写倍增吧,代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=2e4+10;
int n,m;
int jump[M][17],dep[M];
ll f[M][17][62],lb[62];
ll val[M];
struct ss{
    int to,last;
    ss(int a=0,int b=0):to(a),last(b){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b){
    g[++cnt]=ss(b,head[a]);head[a]=cnt;
    g[++cnt]=ss(a,head[b]);head[b]=cnt;
}
void insert(ll *a,ll x){
    for(int i=60;i>=0;i--){
        if(!((x>>i)&1ll)) continue;//这里注意continue
        if(!a[i]){a[i]=x;return;}
        else x^=a[i];
    }//线性基插入
}
void merge(ll *a,ll *b){
    for(int i=60;i>=0;i--) if(b[i]) insert(a,b[i]);
}//线性基合并
void dfs(int a,int fa){
    jump[a][0]=fa;
    dep[a]=dep[fa]+1;
    for(int i=1;i<=16;i++)
        if(jump[a][i-1]){
        	jump[a][i]=jump[jump[a][i-1]][i-1];
        	memcpy(f[a][i],f[a][i-1],sizeof(f[a][i-1]));
        	merge(f[a][i],f[jump[a][i-1]][i-1]);
        }
    for(int i=head[a];i;i=g[i].last){
        if(g[i].to==fa) continue;
        dfs(g[i].to,a);
    }
}//倍增
int lca(int a,int b){
    if(dep[a]<dep[b]) swap(a,b);
    int dist=dep[a]-dep[b];
    for(int i=16;i>=0;i--){
        if((dist>>i)&1) merge(lb,f[a][i]),a=jump[a][i];
    }
    if(a==b) return a;
    for(int i=16;i>=0;i--){
        if(jump[a][i]!=jump[b][i]){
            merge(lb,f[a][i]);merge(lb,f[b][i]);
            a=jump[a][i];b=jump[b][i];
        }
    }
    merge(lb,f[a][0]);
    insert(lb,val[b]);
    return jump[a][0];
}
ll a;
int b,c;
void file(){
    freopen("lucky.in","r",stdin);
    freopen("lucky.out","w",stdout);
}
void close(){
    fclose(stdin);
    fclose(stdout);
}
int main(){
	file();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a);
        val[i]=a;
        insert(f[i][0],a);
    }
    for(int i=1;i<n;i++){
        scanf("%d%d",&b,&c);
        add(b,c);
    }
    dfs(1,0);
    while(m--){
        memset(lb,0,sizeof(lb));
        scanf("%d%d",&b,&c);
        int gfa=lca(b,c);
        merge(lb,f[gfa][0]);
        ll ans=0;
        for(int i=60;i>=0;i--) ans=max(ans,ans^lb[i]);
        printf("%lld\n",ans);
    }
	close();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值