P4098 [HEOI2013] ALO

[HEOI2013] ALO

题目描述

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

现在你拥有 \(n\) 颗宝石,第 \(i\) 颗宝石有一个能量密度,记为 \(a_i\),这些宝石的能量密度两两不同。现在你可以选取连续的一些宝石(必须多于一个)进行融合,设他们的能量密度为 \(a_i,a_{i+1},\cdots,a_j\),则融合而成的宝石的能量密度为这些宝石中能量密度的次大值与其他任意一颗宝石的能量密度按位异或的值的最大值。即,假设该段宝石能量密度次大值为 \(k\),则生成的宝石的能量密度为 \(\max\{k\oplus a_p\mid a_p\ne k, i\le p\le j\}\)

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

输入格式

第一行,一个整数 \(n\),表示宝石个数。

第二行,\(n\) 个整数,分别表示 \(a_1\)\(a_n\),表示每颗宝石的能量密度,保证对于 \(i\ne j\)\(a_i\ne a_j\)

输出格式

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

样例 #1

样例输入 #1

5 
9 2 1 4 7

样例输出 #1

14

提示

样例解释

选择区间 \([1,5]\),最大值为 \(7\oplus 9 = 14\)

数据规模与约定

  • 对于 \(20\%\) 的数据有 \(n\le 100\)
  • 对于 \(50\%\) 的数据有 \(n\le 2000\)
  • 对于 \(100\%\) 的数据有 \(1\le n\le 50000\)\(0\le a_i\le 10^9\)

2023.4.28:添加两组 hack 数据,不计分。

原题链接

次大值可能感觉不是很好做,但是最大值就还好,因为如果是最大值的话,每一个最大值其实是控制了一个连续的区间的,那么只需要再这个区间上面找到和这个树异或最大的就好了,可持久化trie树的模板题。
那次大值其实也差不多吧,每一个次大值也是控制了一个区间的,我们只用把每个区间找出来,然后在这里面找最大的异或和就好了。

那么怎么找每一个次大值控制的区间范围呢?

如果能够对每个点\(O(logn)\)以下的找到次大值,那就好了。
单调栈好了吧?
能够找到每一个点上一个比它大的点在哪里
嘶,还要找到上两个吧。
没事,对于单调栈来说,很简单。
那么就O(n)的找到了每一个点往前和往后的比它大的前两个点,它的控制范围也在这里面了。就是左右两边到比它大的第二个点为止。
这个区间的找最大异或是可持久化trie的专场,O(1)做了 ?忘记了复杂度多少了
效率应该是很高的。

额,单调栈是错的。。这么简单的东西我都没看出来。。
单调栈找到的不是前两个比它大的,这很明显啊。。然后我写了一个暴力准备对拍,交上去的时候交错了,tm的A了,给我人都整傻了。。
(主要是暴力跑的还飞快
这就是\(O(n^2)\)
前面的找次大值的话应该是用二分的,先二分第一个比它大的,再二分第二个比它大的。。
其实简单的可以用双向链表,然后一个一个删除。
上面的是\(O(nlogn)\)下面的是\(O(n)\),我感觉下面的好写一些。
但是双向链表的比较巧妙,也挺好用的,建议学习一下。
这边挂一个暴力的吧(逃

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
	char c=getchar();ll a=0,b=1;
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
ll n,sta[800001],top,a[800001],tr[800001*32][2],last[800001*32],front[800001][3],back[800010][3],tot;
ll root[800001*32];
void insert(ll i,ll x,ll k,ll now)
{
	if(k<0)
	{
		last[x]=now;
		return ;
	}
	ll c=a[now]>>k&1;
	if(i)tr[x][c^1]=tr[i][c^1];
	tr[x][c]=++tot;
	insert(tr[i][c],tr[x][c],k-1,now);
	last[x]=max(last[tr[x][0]],last[tr[x][1]]);
}
ll ask(ll now,ll val,ll k,ll lim)
{
	if(k<0)
	{
		return a[last[now]]^val;
	}
	ll c=val>>k&1;
	if(last[tr[now][c^1]]>=lim)return ask(tr[now][c^1],val,k-1,lim);
	else return ask(tr[now][c],val,k-1,lim);
}
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();ll Max=0;
	for(ll i=1;i<=n;i++)
	{
		a[i]=read();
		Max=max(Max,a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		int count=0;int j;
		for(j=i-1;j>=1;j--)
		{
			if(a[j]>a[i])front[i][++count]=j;
			if(count==2)break;
		}
	}
	for(int i=1;i<=n;i++)
	{
		int count=0;int j;
		for(j=i+1;j<=n;j++)
		{
			if(a[j]>a[i])back[i][++count]=j;
			if(count==2)break;
		}
	}
//	for(ll i=1;i<=n;i++)失败的单调栈
//	{
//		while(top>0&&a[i]>a[sta[top]])top--;
//		if(top!=0)front[i][1]=sta[top];
//		if(top>1)front[i][2]=sta[top-1];
//		sta[++top]=i;
//	}
//	top=0;
//	for(ll i=n;i>=1;i--)
//	{
//		while(top>0&&a[i]>a[sta[top]])top--;
//		if(top!=0)back[i][1]=sta[top];
//		if(top>1)back[i][2]=sta[top-1];
//		sta[++top]=i;
//	}
	for(ll i=1;i<=n;i++)
	{
		if(back[i][1]==0)back[i][1]=n+1;
		if(back[i][2]==0)back[i][2]=n+1;
	}
	last[0]=-1;
	root[0]=++tot;
	insert(0,root[0],31,0);
	for(ll i=1;i<=n;i++)
	{
		root[i]=++tot;
		insert(root[i-1],root[i],31,i);
	}
	ll ans=0;
	for(ll i=1;i<=n;i++)
	{
		ll st,ed;
		ed=back[i][2]-1;
		st=front[i][2]+1;
		if(Max!=a[i])
			ans=max(ans,ask(root[ed],a[i],31,st));
	}
	cout<<ans<<endl;
	return 0;
}

我来说说这题的我遇到的坑点吧(我为什么这题能调一天吧)
1.开了个k=32,然后没开longlong,结果调了半天,硬是样例过不去。。(01trie树的写法问题)
2.就是上面的说的次大值的问题
3.好像没啥了

这个用链表求次大值的方法是很值得学习的,它是能推广到前面第n个比它大的这种的。
但是有一个要求,就是不能对顺序由要求,就是不要强制在线,但是这个可以预处理,复杂度O(n),所以也没啥。
唉唉,写完这题,对trie树是熟悉了,一天也没了。。。

posted @ 2024-01-30 12:12  HL_ZZP  阅读(1)  评论(0编辑  收藏  举报