P5283 [十二省联考2019]异或粽子(主席树+最大异或和)

传送门

题意:从所有区间长度大于l小于r的区间段中,选择最大的k个区间异或合相加,输出答案

这道题与P2048 [NOI2010]超级钢琴的思想相似,如果没有做过可以先去尝试一下超级钢琴。

 P2048 [NOI2010]超级钢琴: 传送门

题解:由于异或一个相同的数,异或值为0所以对于前缀和来说,sum[r]XORsum[l-1]其实就是r到(l-1)的异或合,所以对于求的第几大值我们可以固定左端点,通过最大异或合的思想去求范围内的第k大的异或合

AC代码:

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<queue>
#define ll long long
using namespace std;
const int maxn=5e5+5;
//const ll inf=9223372036854775800;
//const int inf=1e5+5;
inline ll read(){
    ll x=0,w=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')w=0,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
struct node{
    ll su;
    int k,p;
    friend bool operator < (node a,node b){
        return a.su<b.su;
    }
};
priority_queue<node> q;
int ls[maxn*80],rs[maxn*80],num1[maxn*80],num0[maxn*80],ti[maxn],tot;
ll er[35],ans;
int ai[maxn][35];
//0在左子树,1在右子树
inline void add(int*now,int lst,int len,int k,ll sum){
    while(len>=0){
    *now=++tot;
    ls[*now]=ls[lst];
    rs[*now]=rs[lst];
    num1[*now]=num1[lst];
    num0[*now]=num0[lst];
    ai[k][len]=(sum>>len)&1;
    if(ai[k][len])
        num1[*now]++,now=&rs[*now],lst=rs[lst],len--;
    else
        num0[*now]++,now=&ls[*now],lst=ls[lst],len--;
    }
}
inline void query(int now,int lst,int len,int k,int k1){
    //printf("%lld %d %d %d %d %d\n",ans,len,nu0,nu1,now,lst);
    int nu0,nu1;
    while(len>=0){
    nu0=num0[now]-num0[lst];
    nu1=num1[now]-num1[lst];
    if(ai[k1][len]){
        if(nu0>=k){
            ans+=er[len];
            now=ls[now];
            lst=ls[lst];
            len--;
        }
        else{
            now=rs[now];
            lst=rs[lst];
            len--;
            k-=nu0;
        }
    }
    else{
        if(nu1>=k){
            ans+=er[len];
            now=rs[now];
            lst=rs[lst];
            len--;
        }
        else{
            now=ls[now];
            lst=ls[lst];
            len--;
            k-=nu1;
        }
    }
    }
}
int main( ){
    er[0]=1;
    for(int a=1;a<=34;a++)
        er[a]=er[a-1]*2;
    ll n=read(),k=read();
    ll sum=0;
    tot=ti[0]=ls[0]=rs[0]=num1[0]=num0[0]=0;
    memset(ai[0],0,sizeof(ai[0]));
    for(int a=1;a<=n;a++){
        sum^=read();
        add(&ti[a],ti[a-1],34,a,sum);
    }
    for(int a=1;a<=n;a++){
        ans=0;
        query(ti[n],ti[a-1],34,1,a-1);
        node now={ans,1,a};
        q.push(now);
    }
    ll ans1=0;
    while(k){
        node now=q.top();
        q.pop();
        ans1+=now.su;
        k--;
        if(n-now.p+1<=now.k)
            continue;
        ans=0;
        query(ti[n],ti[now.p-1],34,now.k+1,now.p-1);
            now.su=ans;
            now.k++;
            q.push(now);
    }
    printf("%lld\n",ans1);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值