题目传送门
题目大意
给出一个长为n的序列,选择k个不完全重合的区间使得每个区间的异或值的总和最大。
题解
作为高三退役狗康复训练的第一题,我发现我的做法有点特立独行……
4月份Ghastlcon跟我说这题的时候,我在想为什么要用可持久化Trie。而现在我YY出了一个既不用可持久化也不用堆的方法。
首先把所有的异或前缀和扔进Trie,接下来目标就是找最大的k对异或值。
接下来考虑根节点的两个儿子的size(即endpos的个数),记为m和n。
一:如果
m
∗
n
m*n
m∗n小于等于k,那么这
m
∗
n
m*n
m∗n对是一定要取的,暴力贡献答案后令
k
−
=
m
∗
n
k-=m*n
k−=m∗n。剩下的那些对分别从根的两棵子树取。
二:如果 m ∗ n m*n m∗n大于k,那么k对的最高位必定都为1,先贡献进答案再说。然而有一个限制,就是剩下的位里只能一棵在根的0子树,另一棵在1子树。
这个限制不好搞,继续分类讨论。第二种情况中,我们接下来要看根的0子树的1子树的size乘根的1子树的0子树的size,再加根的0子树的0子树的size乘根的1子树的1子树的size和k的关系。有点绕口,不妨简记为 s ( 01 ) ∗ s ( 10 ) + s ( 00 ) ∗ s ( 11 ) s(01)*s(10)+s(00)*s(11) s(01)∗s(10)+s(00)∗s(11)。
如果它小于等于k,仿照一的做法,我们暴力计算这些对,剩下的对就要从00,10和01,11中取。如果它大于k,那么答案的k对里这一位全为1,限制增强为剩下的位要从01,10或00,11中取。
现在我们试图总结出一个稍微程序化的过程,它本质上有点像trie上的双路DP。只不过因为我们按层依次剖分了这颗trie,所以当前构造的两条路径的端点一定位于同一层。而且在我们处理某一层时,这一层有很多个“双路端点对”,用一个数组记录下来即可。
具体实现时,就是把上一层的端点对拿出来,看一下他们的0,1子树size分别对应相乘之和与当前k的关系(注意两个端点重合的情况以及k可能不断变小)。如果小于等于k,暴力取出这些对贡献答案,然后剩下的对在端点对的0,0或1,1子树中找。如果大于k,则限制增强为对应端点对的0,1或1,0子树中找。
显然trie上的一个点只会被处理一次,时间复杂度
O
(
(
n
+
k
)
∗
l
o
g
(
m
a
x
v
a
l
)
)
O((n+k)*log(maxval))
O((n+k)∗log(maxval))。后来看到别人都用了更加简单粗暴的方法(自己找来看吧,可持久化+堆)。不过实际运行时间上这种方法貌似更好:
(无聊看了看status发现自己居然rk1!)
code
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=500100;
const int maxl=33;
typedef unsigned int UI;
typedef long long LL;
struct Tnode
{
Tnode *son[2];
int id,Size;
} tree[maxn*maxl];
Tnode *Root;
int cur=-1;
UI f[maxn];
UI a[maxn];
Tnode *p[maxn];
Tnode *q[maxn];
int num=1;
Tnode *tp[maxn];
Tnode *tq[maxn];
int temp;
UI A[maxn];
UI B[maxn];
int nA,nB;
LL sum,ans=0;
int n,k;
Tnode *New_node()
{
cur++;
tree[cur].son[0]=tree[cur].son[1]=NULL;
tree[cur].id=-1;
tree[cur].Size=0;
return tree+cur;
}
void Insert(Tnode *P,UI val,int w,int Id)
{
P->Size++;
if (w<0)
{
P->id=Id;
return;
}
UI v=1;
v=(v<< ((unsigned int)w) );
UI d=(val&v);
if (d>0) d=1;
if (!P->son[d]) P->son[d]=New_node();
Insert(P->son[d],val,w-1,Id);
}
LL Get(Tnode *P)
{
if (!P) return 0;
return P->Size;
}
void Dfs(Tnode *P,UI *C,int &v)
{
if (!P) return;
if (P->id>=0)
for (int i=1; i<=P->Size; i++) C[++v]=f[P->id];
Dfs(P->son[0],C,v);
Dfs(P->son[1],C,v);
}
void Calc(Tnode *P,Tnode *Q,int w)
{
if ( !P || !Q ) return;
nA=nB=0;
Dfs(P,A,nA);
Dfs(Q,B,nB);
LL v=1;
w++;
v=(v<< ((long long)w) );
v--;
for (int i=1; i<=nA; i++) A[i]=(A[i]&v);
for (int j=1; j<=nB; j++) B[j]=(B[j]&v);
for (int i=1; i<=nA; i++)
for (int j=1; j<=nB; j++) ans+=( A[i]^B[j] );
}
void Push(Tnode *P,Tnode *Q)
{
if ( !P || !Q ) return;
temp++;
tp[temp]=P;
tq[temp]=Q;
}
void Work(int w)
{
if ( w<0 || k<=0 || num<=0 ) return;
sum=0;
for (int i=1; i<=num; i++)
if (p[i]==q[i]) sum+=( Get(p[i]->son[0])*Get(p[i]->son[1]) );
else
{
sum+=( Get(p[i]->son[0])*Get(q[i]->son[1]) );
sum+=( Get(q[i]->son[0])*Get(p[i]->son[1]) );
}
temp=0;
if (sum<=k)
{
k-=sum;
for (int i=1; i<=num; i++)
{
if (p[i]==q[i]) Calc(p[i]->son[0],p[i]->son[1],w);
else
{
Calc(p[i]->son[0],q[i]->son[1],w);
Calc(q[i]->son[0],p[i]->son[1],w);
}
Push(p[i]->son[0],q[i]->son[0]);
Push(p[i]->son[1],q[i]->son[1]);
}
}
else
{
ans+=( ((long long)k)<<w );
for (int i=1; i<=num; i++)
if (p[i]==q[i]) Push(p[i]->son[0],p[i]->son[1]);
else
{
Push(p[i]->son[0],q[i]->son[1]);
Push(q[i]->son[0],p[i]->son[1]);
}
}
num=temp;
for (int i=1; i<=num; i++) p[i]=tp[i],q[i]=tq[i];
Work(w-1);
}
int main()
{
scanf("%d%d",&n,&k);
f[0]=0;
for (int i=1; i<=n; i++) scanf("%u",&a[i]),f[i]=(f[i-1]^a[i]);
Root=New_node();
for (int i=0; i<=n; i++) Insert(Root,f[i],31,i);
p[1]=q[1]=Root;
Work(31);
printf("%lld\n",ans);
return 0;
}