To_Heart—题解——[HEOI2013]ALO
题目链接
题解
对于每一个数,我们先考虑它可以和哪些数异或。
设当前这个数为 i i i,如果我们能够找到左边的第二个 l l l 使得 a i < a l a_i<a_l ai<al,那么 i i i 就是区间 [ l + 1 , i ] \left[ l+1,i \right] [l+1,i] 的次大值,同理往右边找到第二个 r r r 使得 a i < a r a_i<a_r ai<ar , i i i 就是区间 [ i , r − 1 ] \left[ i,r-1 \right] [i,r−1] 的次大值。这两个区间的交集就是所有可以与 a i a_i ai 进行异或的 a j a_j aj。
那么如何找出来我们要的 l l l 和 r r r 呢? 考虑用链表连接整个区间,从小到大的查找每一个数的 l l l 和 r r r,也就是前驱的前驱 和 后驱的后驱,并在结束时将当前的点丢出链表。因为排序,所以这样处理的复杂度是 O ( n log n ) O(n\log n) O(nlogn)的。
对于每一个区间,我们可以把所有的 a a a 放进一颗字典树上,然后在字典树上查找所有的 a a a 与 a i a_i ai 异或的最大值。
因为区间始终是在 [ 1 , n ] \left[ 1,n \right] [1,n] 里面的,所以可以考虑可持久化,于是每次查询的复杂度就降成了 O ( log n ) O(\log n) O(logn)
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,ans=0;
int a[6000005],tot=0;
int root[20000005];
struct Trie{
int t[80000005][2],cnt=0;
int sum[80000005];
void Change_Tree(int l,int &r,int val){
r=++cnt,sum[r]=sum[l]+1;
int x=l,y=r;
for(int i=30;i>=0;i--){
int op=((val>>i)&1);
t[y][!op]=t[x][!op];
t[y][op]=++cnt,sum[t[y][op]]=sum[t[x][op]]+1;
x=t[x][op],y=t[y][op];
}
}
int Find_Tree(int x,int y,int val){
if(x>y) return 0;
int ans=0;
for(int i=30;i>=0;i--){
int op=((val>>i)&1);
if(sum[t[y][!op]]-sum[t[x][!op]]>0) ans+=(1<<i),op=!op;
x=t[x][op],y=t[y][op];
}
return ans;
}
}T;
int lsh[50005];
int val[50005];
int pre[50005],nxt[50005];
struct qwq{
int val,id;
}t[50005];
bool cmp(qwq x,qwq y){
return x.val<y.val;
}
int L[50005],R[50005];
int main(){
cin>>n;for(int i=1;i<=n;i++) scanf("%d",&a[i]),t[i].val=a[i],t[i].id=i,pre[i]=i-1,nxt[i]=i+1,T.Change_Tree(root[i-1],root[i],a[i]);
pre[1]=1;nxt[n]=nxt[n+1]=nxt[n+2]=n;
sort(t+1,t+n+1,cmp);
for(int i=1;i<=n;i++){
int id=t[i].id;
L[id]=pre[pre[id]],R[id]=nxt[nxt[id]];
nxt[pre[id]]=nxt[id],pre[nxt[id]]=pre[id];
}
for(int i=1;i<=n;i++) ans=max(ans,max(T.Find_Tree(root[L[i]-1],root[i-1],a[i]),T.Find_Tree(root[i+1],root[R[i]-1],a[i])));
printf("%d\n",ans);
return 0;
}