To_Heart—题解——[HEOI2013]ALO

题目链接

Link.

题解

对于每一个数,我们先考虑它可以和哪些数异或。

设当前这个数为 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;
}

上一篇:luoguP1434 [SHOI2002]:滑雪


下一篇:运算符