【简】题解 P5283 [十二省联考2019]异或粽子

传送门:P5283 [十二省联考2019]异或粽子

题目大意:

给一个长度为n的数列,找到异或和为前k大的区间,并求出这些区间的异或和的代数和。

QWQ:

考试时想到了前缀异或 想到了对每个数按二进制拆分 最高位取一定比前面所有取优

但是呆住了 没有想到是对前缀异或拆分

对于位运算等操作可以考虑 线性基和trie

因为 ai xor aj=aj xor ai 所以吧这种情况算进去就取ans/2

因为 i​=j 时异或为0是最小的 不会影响答案

把各个前缀异或插进数组

询问强制以每个点为前面的一个端点 查询第x大的值

先把所有的第1大的插进堆里

每次取堆中最大 统计答案

当前取出的最大是强制的区间第x大 把区间第x+1大插入队里

代码:

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
#define R register
const ll N=20000000+10;
ll n,k,ans;
ll a[N],sum[N],sz[N];
ll tr[N][2],top;
struct xin{
    ll w,cs,v;
    friend bool operator < (xin a,xin b)
    {
        return a.v<b.v;
    }
};
priority_queue<xin>q;
inline void into(ll v)
{
    int u=0;
    for(int i=31;i>=0;i--)
    {
        ll tmp=(v>>i)&1;sz[u]++;
        if(!tr[u][tmp]) tr[u][tmp]=++top;
        u=tr[u][tmp];
    }
    sz[u]++;
}
inline ll ask(ll v,int cs)
{
    ll u=0,ans=0;
    for(int i=31;i>=0;i--)
    {
        ll tmp=(v>>i)&1;
        if(!tr[u][tmp^1]){u=tr[u][tmp];continue;}
        if(cs<=sz[tr[u][tmp^1]]){u=tr[u][tmp^1];ans|=1LL<<i;continue;}
        if(cs>sz[tr[u][tmp^1]]){cs-=sz[tr[u][tmp^1]];u=tr[u][tmp];continue;}
    }
    return ans;
}
int main()
{
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout); 
    n=read();k=read();k<<=1;
    for(R int i=1;i<=n;i++) a[i]=read();
    for(R int i=1;i<=n;i++) sum[i]=sum[i-1]^a[i];
    for(int i=0;i<=n;i++) into(sum[i]);
    for(int i=0;i<=n;i++) q.push((xin){i,1,ask(sum[i],1)});
    for(int i=1;i<=k;i++)
    {
        xin tmp=q.top();q.pop();ans+=tmp.v;
        if(tmp.cs<n) q.push((xin){tmp.w,tmp.cs+1,ask(sum[tmp.w],tmp.cs+1)});
    }
    cout<<(ans>>1)<<endl;
    return 0;
}

 

posted @ 2019-07-15 21:53  1436177712  阅读(163)  评论(0编辑  收藏  举报