[SCOI2016][JZOJ4632]幸运数字

题目大意

一棵 n 个点的树,每个点有点权Gi
q 个询问,每次询问在点x到点 y 路径上,选择一些点,使得异或和最大,这个最大和为多少。
1n2×104,1q2×105,Gi[0,260)


题目分析

对于小数据我们可以想到一种高斯消元解异或方程组的解法。从高位到低位,先假设当前位有 1 ,然后在方程组中进行消元,如果没有冲突就填,有就填0,然后更新方程组。
但是满分数据很大,这样做是不行的。这里我们就要用到一种叫做线性基的强大的东西。我们先来看看什么叫线性基:
乱搞
啊不~不是这个(这么说我就是神犇了?哈哈哈哈哈~~~)。
线性基是什么呢?就是给定一个二进制集合 S ,然后我们要求出一个最小的集合s,这个集合内任意子集异或和能构成的数的集合,和原本二进制集合任意子集异或和能构成的数的集合是一样的。
说白了就是高斯消元解异或方程组最后剩下的矩阵。
换句话说利用线性基内的互相异或,就可以得出原集合互相异或的结果。如果二进制位数为 d ,那么线性基大小显然不超过d
可以证明,线性基内不存在异或和为 0 的子集。
我们采用d d 位二进制数代表一个线性基。第i位如果有数,那数一定是线性基里面第 d 1的最小的数。
那么我们查询最大异或和时可以直接从高位到低位贪心,如果异或上线性基第 i 位能使答案更大就异或,否则不异或。
线性基的合并就是和高斯消元差不多,对于第二个线性基的每一个待插入数,我们枚举第一个线性基里的位置d,如果该位置有数且当前待插入数 d 位为1,就把待插入数和该位异或,如果该位置没有数且待插入数 d 位为1,就将待插入数插到这里。
设二进制有 D 位,那么合并线性基的时间复杂度就是O(D2)
回到这题,使用倍增维护树上区间的线性基,暴力合并即可。
注意到这里合并线性基的复杂度很大,如果合并多次是很难承受的。因此这里我们要使用类 RMQ 的查询思想,分成四个区间(重叠部分不会影响答案),然后暴力合并。
总时间复杂度 O(nlog2nD2+q(log2n+D2))


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=20050;
const int M=N<<1;
const int LGN=15;
const int D=60;

struct linear_basic
{
    LL num[D];
};

typedef linear_basic LB;

LB operator+(LB x,LB y)
{
    for (int i=D-1;i>=0;i--)
        if (y.num[i])
            for (int j=D-1;j>=0;j--)
            {
                if (!y.num[i]) break;
                if (y.num[i]>>j&1)
                    if (x.num[j]) y.num[i]^=x.num[j];
                    else
                    {
                        x.num[j]=y.num[i];
                        break;
                    }
            }
    return x;
}

int last[N],high[N];
int tov[M],next[M];
int n,lgn,tot,q;
int fa[N][LGN];
LB f[N][LGN];

void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
    for (int i=last[x],y;i;i=next[i])
        if ((y=tov[i])!=fa[x][0])
            fa[y][0]=x,high[y]=high[x]+1,dfs(y);
}

void pre()
{
    lgn=trunc(log(n)/log(2));
    for (int j=1;j<=lgn;j++)
        for (int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1],f[i][j]=f[i][j-1]+f[fa[i][j-1]][j-1];
}

int lca(int x,int y)
{
    if (high[x]>high[y]) swap(x,y);
    for (int i=lgn;i>=0;i--)
        if (high[fa[y][i]]>=high[x]) y=fa[y][i];
    if (x==y) return x;
    for (int i=lgn;i>=0;i--)
        if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int p(int x,int h)
{
    for (int i=lgn;i>=0;i--)
        if (high[fa[x][i]]>=h) x=fa[x][i];
    return x;
}

LL query(int x,int y)
{
    int z=lca(x,y);
    int lgh;
    lgh=trunc(log(high[x]-high[z]+1)/log(2));
    LB lb0=f[x][lgh]+f[p(x,high[z]+(1<<lgh)-1)][lgh];
    lgh=trunc(log(high[y]-high[z]+1)/log(2));
    lb0=lb0+f[y][lgh]+f[p(y,high[z]+(1<<lgh)-1)][lgh];
    LL ret=0;
    for (int i=D-1;i>=0;i--) if ((ret^lb0.num[i])>ret) ret^=lb0.num[i];
    return ret;
}

int main()
{
    freopen("lucky.in","r",stdin),freopen("lucky.out","w",stdout);
    n=read(),q=read();
    for (int i=1;i<=n;i++)
    {
        LL x;
        scanf("%lld",&x);
        for (int j=D-1;j>=0;j--)
            if (x>>j&1)
            {
                f[i][0].num[j]=x;
                break;
            }
    }
    for (int i=1,x,y;i<n;i++)
    {
        x=read(),y=read();
        insert(x,y),insert(y,x);
    }
    dfs(1),pre();
    for (int i=1,x,y;i<=q;i++)
    {
        x=read(),y=read();
        printf("%lld\n",query(x,y));
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值