bzoj 5495
今年省选题...
表示当时还没学可持久化trie,所以打60分暴力走人...
现在学了可持久化字典树,就可以搞一搞了嘛!
首先看到题目描述,很容易想到首先搞出异或前缀和,然后建起可持久化字典树
然后考虑一个问题:怎么找出每次的最优区间呢?
因为只有给出一个区间,我们才能利用可持久化trie去跑最大异或和
所以我们改良一下算法:我们枚举每个区间右端点,然后向左找一个端点,求出最大的异或和。然后把所有这些异或和扔进一个大根堆里即可,每次找出堆顶累计进答案,然后对于堆顶对应的右端点,我们找出以他为右端点,次小的异或和再扔进大根堆里,以此类推,直至求出前k大值为止
贴代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long using namespace std; struct Trie { int to[2]; int ed; }tree[20000005]; struct node { ll v; int rq; int num; friend bool operator < (node a,node b) { return a.v<b.v; } }; priority_queue <node> M; int rt[500005]; ll a[500005]; int n,k; int tot=0; void ins(ll x,int now,int las) { rt[now]=++tot; now=rt[now],las=rt[las]; for(int i=32;i>=0;i--) { tree[now]=tree[las]; tree[now].ed++; if((x>>i)&1)tree[now].to[1]=++tot,now=tree[now].to[1],las=tree[las].to[1]; else tree[now].to[0]=++tot,now=tree[now].to[0],las=tree[las].to[0]; } tree[now].ed=tree[las].ed+1; } ll query(int lq,int rq,ll x,int rk,int temp) { if(temp==-1)return 0; int t=((x>>temp)&1)?0:1; int sum=tree[tree[rq].to[t]].ed-tree[tree[lq].to[t]].ed; if(sum>=rk)return query(tree[lq].to[t],tree[rq].to[t],x,rk,temp-1)+(1ll<<temp); else return query(tree[lq].to[t^1],tree[rq].to[t^1],x,rk-sum,temp-1); } int main() { scanf("%d%d",&n,&k); n++; ins(0,1,0); for(int i=2;i<=n;i++)scanf("%lld",&a[i]),a[i]^=a[i-1],ins(a[i],i,i-1); for(int i=2;i<=n;i++)M.push((node){query(rt[0],rt[i],a[i],1,32),i,1}); ll ans=0; for(int i=1;i<=k;i++) { node temp=M.top(); M.pop(); ans+=1ll*temp.v; if(temp.num<temp.rq)M.push((node){query(rt[0],rt[temp.rq],a[temp.rq],temp.num+1,32),temp.rq,temp.num+1}); } printf("%lld\n",ans); return 0; }