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

题目大意

传送门
给定一个长为n的序列, 最大化选出k个不同区间异或和的和
\(n,k<=5*10^5\)

solution

先讲套路: 看到这类跟位运算有关的题,先想想Trie树

\(S_i\) 为[1...i]区间的异或和,则任意一个区间[l,r]异或和为 \(S_{l-1}\) xor \(S_r\)
考虑每个节点 i 作为右端点时第一次选择可能的贡献:
选出1个节点j (j<=i) 作为左端点: 最大化 \(S_j xor S_i\)
然后需要在所有节点中找出贡献最大的节点
那么下一次该点i可能的有贡献点对就必须是 选出j,满足 \(S_j xor S_i\) 为次大值
以此类推...

所以我们建一个大根堆,先把所有点作为右端点的最大贡献点对加入
然后对每个节点为记录其使用过了几次
每删除一个堆顶,记录贡献之后
若该点已使用cnt次,就将以该点为右端点的第cnt大贡献点对加入

code

#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
#define int unsigned
in int read()
{
    int t=0; char ch=get;
    while(ch<'0' || ch>'9') ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t;
}
const int _=1e6+23;
struct Trie{
    int siz,ch[2];
}tr[_<<6];
int rt[_<<2], tot, s[_], n, m,now[_];
#define ls(k) tr[k].ch[c]
#define rs(k) tr[k].ch[1^c]
in void insert(int a,short x,int k1,int k2)
{
    if(x<0) { tr[k2].siz=tr[k1].siz+1; return;}
    int c=a>>x&1;
    if(k1) rs(k2)=rs(k1);
    ls(k2)=++tot;
    insert(a,x-1,ls(k1),ls(k2));
    tr[k2].siz=tr[ls(k2)].siz+tr[rs(k2)].siz;
}
in int query(int a,short x,int k,int lim)
{
    if(x<0){ return 0;}
    int c=a>>x&1;
    if(tr[rs(k)].siz>=lim) return query(a,x-1,rs(k),lim)|(1<<x);
    else return query(a,x-1,ls(k),lim-tr[rs(k)].siz);
}
#define mp make_pair
signed main()
{
#ifndef ONLINE_JUDGE
    freopen("1.in","r",stdin);
#endif
    n=read(), m=read();
    rt[0]=++tot;
    insert(0,31,0,rt[0]);
    for(re int i=1;i<=n;++i)
    {
        int x=read(); s[i]=s[i-1]^x;
        rt[i]=++tot;
        insert(s[i],31,rt[i-1],rt[i]);
    }
    ll ans=0; priority_queue<pair<int,int> >q; //第一维是贡献,第二维是区间右端点
    for(re int i=1;i<=n;++i) {q.push(mp(query(s[i],31,rt[i],1),i)); now[i]=1;}
    while(m--)
    {
        ans+=q.top().first; int u=q.top().second; q.pop();
        q.push(mp(query(s[u],31,rt[u],++now[u]),u));
    }
    cout<<ans<<endl;
    return 0;
}

posted @ 2021-04-03 22:34  yzhx  阅读(48)  评论(0编辑  收藏  举报