P4098 [HEOI2013]ALO

时间:2023-03-09 18:28:29
P4098 [HEOI2013]ALO

最近这个家伙去哪了,为啥一直不更博客了呢?原来他被老师逼迫去补了一周的文化课,以至于不会把班里的平均分拉掉太多。好了,我们来看下面这道题目:

P4098 [HEOI2013]ALO

题目描述

Welcome to ALO ( Arithmetic and Logistic Online)。这是一个 VR MMORPG, 如名字所见,到处充满了数学的谜题

现在你拥有 n 颗宝石,每颗宝石有一个能量密度,记为 ai,这些宝石的能量 密度两两不同。现在你可以选取连续的一些宝石(必须多于一个)进行融合,设 为 ai, ai+1, …, aj,则融合而成的宝石的能量密度为这些宝石中能量密度的次大值 与其他任意一颗宝石的能量密度按位异或的值,即,设该段宝石能量密度次大值 为 k,则生成的宝石的能量密度为 max{k xor ap | ap ≠ k , i ≤ p ≤ j}

现在你需要知道你怎么选取需要融合的宝石,才能使生成的宝石能量密度最 大

输入输出格式

输入格式:

第一行,一个整数 n,表示宝石个数

第二行,n 个整数,分别表示 a1 至 an,表示每颗宝石的能量密度,保证对于 i ≠ j 有 ai ≠ aj

输出格式:

输出一行一个整数,表示最大能生成的宝石能量密度

输入输出样例

输入样例#1:
5
9 2 1 4 7
输出样例#1:
14

首先关于异或和什么的问题,一看就要用到trie,然而又是有区间限制,所以一定要访问某个历史版本,自然就是可持久化trie了。那么最重要的问题来了,我们如何找到以每个数为次大值最大的区间呢?首先我们可以想到,如果一个区间里一个数为次大值,那么最大值要么在这个数的左边,要么在这个数的右边,所以我么就可以维护每个数左边比它大的两个数l1、l2,和右边比这个数大的两个数r1、r2这样区间就是(l1,r2)和(l2,r1)这两个区间,那么如何求解每个数的l1,l2,r1,r2呢,这里有两种方法。

第一种,我们可以用ST表预处理然后左右进行二分查找,但是考虑到有些数可能没有l1,l2,r1,r2,这样就需要进行特判,细节巨多无比所以不建议用这种做法。

 #include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#define maxn 50005
using namespace std; inline int read()
{
int x=,res=;
char c=getchar();
while(c<''||c>'')
{
if(c=='-')
x=-;
c=getchar();
}
while(c>=''&&c<='')
{
res=res*+(c-'');
c=getchar();
}
return res*x;
} int n,tot,ans;
int a[maxn],f[maxn][],lg[maxn],tree[maxn*][],last[maxn*];
int root[maxn];
int l,r; int zuo(int l,int r,int rr)
{
if(l>r) return ;
int s=lg[r-l+];
int u=max(f[l][s],f[r-(<<s)+][s]);
if(u<=a[rr])
{
return ;
}
else
{
while(l<=r)
{
int mid=(l+r)>>;
s=lg[r-mid+];
if(max(f[mid][s],f[r-(<<s)+][s])>a[rr])
{
l=mid+;
}
else
{
r=mid-;
}
}
return r;
}
} int you(int l,int r,int ll)
{
if(l>r) return ;
int s=lg[r-l+];
int u=max(f[l][s],f[r-(<<s)+][s]);
if(u<=a[ll])
{
return ;
}
else
{
while(l<=r)
{
int mid=(l+r)>>;
s=lg[mid-l+];
if(max(f[l][s],f[mid-(<<s)+][s])>a[ll])
{
r=mid-;
}
else
{
l=mid+;
}
}
return l;
}
} int ask(int l,int val,int k,int now)
{
if(k<) return val^a[last[now]];
int c=(val>>k)&;
if(last[tree[now][c^]]>=l)
{
return ask(l,val,k-,tree[now][c^]);
}
else
{
return ask(l,val,k-,tree[now][c]);
}
} void trie(int i,int k,int l,int r)
{
if(k<)
{
last[r]=i;
return;
}
int c=(a[i]>>k)&;
if(l) tree[r][c^]=tree[l][c^];
tree[r][c]=++tot;
trie(i,k-,tree[l][c],tree[r][c]);
last[r]=max(last[tree[r][]],last[tree[r][]]);
} int main()
{
n=read();lg[]=-;
last[]=-;
for(int i=;i<=n;i++)
{
a[i]=read();
root[i]=++tot;
trie(i,,root[i-],root[i]);
}
for(int i=;i<=n;i++)
{
f[i][]=a[i];
lg[i]=lg[i>>]+;
}
for(int j=;j<=;j++)
for(int i=;i+(<<j)-<=n;i++)
{
f[i][j]=max(f[i][j-],f[i+(<<(j-))][j-]);
}
for(int i=;i<=n;i++)
{
int l1=-,l2=-,r1=-,r2=-,pd=;
l1=zuo(,i,i);
if(l1!=)
{
l2=zuo(,l1-,i);
if(l2==)
l2=;
}
else
{
l2=;
}
r1=you(i,n,i);
if(r1!=)
{
r2=you(r1+,n,i);
if(r2==)
r2=n+;
}
else
{
pd=;r1=r2=n+;
}
if(l1)
{
ans=max(ans,ask(l2+,a[i],,root[r1-]));
}
if(pd==)
{
ans=max(ans,ask(l1+,a[i],,root[r2-]));
}
}
cout<<ans;
return ;
}

ST表+二分

第二种,我们可以先用双向链表储存每一个数的左右两边的数,然后对序列进行排序,再从最小的元素开始依次查找,每查完一个元素我们就把它从链表里删除,这样链表中存的就一定是每个元素左右两边的第一个比它大的元素那么左边的左边就l2,右边的右边就是r2,这样就实现了查询,另外我们只需特判表头和表尾两个元素就行,实现起来也非常简单。

 #include<iostream>
#include<string>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<map>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#define maxn 50005
using namespace std; struct edge
{
int a,b;
}g[maxn]; inline int read()
{
int x=,res=;
char c=getchar();
while(c<''||c>'')
{
if(c=='-')
x=-;
c=getchar();
}
while(c>=''&&c<='')
{
res=res*+(c-'');
c=getchar();
}
return res*x;
} int n,tot,ans;
int a[maxn],last[maxn*],tree[maxn*][],root[maxn],lx[maxn],rx[maxn]; void trie(int i,int k,int l,int r)
{
if(k<)
{
last[r]=i;
return;
}
int c=(a[i]>>k)&;
if(l) tree[r][c^]=tree[l][c^];
tree[r][c]=++tot;
trie(i,k-,tree[l][c],tree[r][c]);
last[r]=max(last[tree[r][]],last[tree[r][]]);
} int ask(int now,int val,int k,int l)
{
if(k<) return val^a[last[now]];
int c=(val>>k)&;
if(last[tree[now][c^]]>=l)
{
return ask(tree[now][c^],val,k-,l);
}
else
{
return ask(tree[now][c],val,k-,l);
}
} bool cmp(edge x,edge y)
{
return x.a<y.a;
} int main()
{
n=read();
last[]=-;
for(int i=;i<=n;i++)
{
a[i]=read();
g[i].a=a[i];g[i].b=i;
lx[i]=i-;rx[i]=i+;
root[i]=++tot;
trie(i,,root[i-],root[i]);
}
sort(g+,g++n,cmp);
for(int i=;i<=n;i++)
{
int v=g[i].b;
int l=lx[v],r=rx[v];
lx[r]=l;rx[l]=r;
if(l!=)
ans=max(ans,ask(root[r-],g[i].a,,lx[l]+));
if(r!=n+)
ans=max(ans,ask(root[rx[r]-],g[i].a,,l+));
}
cout<<ans;
return ;
}

链表做法

其实还有一个问题,就是在做01trie树的时候,要注意什么时候应该建一棵全0树而什么时候不需要,还有就是last[0]为什么要清为负数,我想这些看似不起眼的问题还是要弄明白比较好,毕竟细节决定成败。