[十二省联考 2019] 异或粽子 题解

发布时间 2023-11-13 21:12:20作者: 2017BeiJiang

只能说相当套路的一道题目。

对于区间异或和,我们不妨先做一遍区间前缀异或和,记作 \(sum_i\),表示 \(a_1\sim a_i\) 的异或和,那么区间 \([l,r]\) 的异或和即可转化为 $sum_r \bigoplus sum_{l-1} $,那么我们呢只需对 \(n+1\) 个数字进行考虑,⚠️注意还有 \(sum_0=0\)

既然题目让我们求 \(k\) 个粽子的最大值,那么我们不妨先求出第一大,再求出第二大,以此类推最后求出第 \(k\) 大,然后将这些全部加起来即可。

总共有 \(\rm O(n^2)\) 个组合,所以我们绝对不可能将所有的组合进行异或,这时不妨考虑另外一个套路:先搞出一个或几个必须选的,然后用这几个推出其他。

但是我们发现这题还有一个隐藏条件,选出来的 \(i\)\(j\) 必须满足 \(i\le j\),这就非常蛋疼,因为这不仅要求找出异或最大的,还要求满足位置上的一定需求。

所以我们不妨将 \(k\times 2\),这样就可以放松这个条件,毕竟如果选了 \((1,2)\) 就必然会同时选上 \((2,1)\),最后再将答案除以 \(2\) 即可。

现在来考虑如何求出必须选的,既然是异或和,那么肯定要用到 \(01Trie\),可以求出对于 \(x\) 找出最大的 \(x\bigoplus sum_i\) 的值,这里不在赘述。对于每个位置,我们找到与其异或最大的,塞进堆里即可。

再来考虑如何通过一个值推出其他。显然,我们可以再先办法对于 \(x\) 求出第 \(k\) 大的总异或和,这样如果从堆里拿出一个排名为 \(k\) 的数,我们在往堆里塞一个排名为 \(k+1\) 的数字即可。

代码:

const int N=5e5+10,M=2e7;
int n,k;
LL a[N],sum[N];
int tot=1,tr[M][2],siz[M];
struct node {
    LL val; int id,rk;
    node(LL a,int b,int c) {
        val=a; id=b; rk=c;
    }
    friend bool operator < (node x,node y) {
        return x.val<y.val;
    }
};
priority_queue<node> q;

void ins(LL x) {
    int nd=1;
    for(int i=32;i>=0;i--) {
        int p=((x&(1ll<<i))!=0);
        siz[nd]++;
        if(tr[nd][p]) nd=tr[nd][p];
        else {tr[nd][p]=++tot; nd=tot;}
    }
    siz[nd]++;
}

LL find(LL x,int rk) {
    int nd=1; LL ans=0;
    for(int i=32;i>=0;i--) {
        int p=((x&(1ll<<i))!=0);
        if(!tr[nd][!p]) nd=tr[nd][p];
        else if(rk<=siz[tr[nd][!p]]) {nd=tr[nd][!p]; ans|=(1ll<<i);}
        else {rk-=siz[tr[nd][!p]]; nd=tr[nd][p];}
    }
    return ans;
}

int main() {
    n=read(); k=read(); k<<=1;
    ins(0);
    for(int i=1;i<=n;i++) {
        a[i]=read(); sum[i]=(sum[i-1]^a[i]); ins(sum[i]);
    }
    for(int i=1;i<=n;i++) {
        q.push({find(sum[i],1),i,1});
    }
    LL ans=0;
    q.push({find(sum[0],1),0,1});
    while(k) {
        auto t=q.top(); q.pop();
        ans=ans+t.val;
        q.push({find(sum[t.id],t.rk+1),t.id,t.rk+1});
        k--;
    }
    cout<<(ans/2);
    return 0;
}