BZOJ #3166. [Heoi2013]Alo(可持久化trie树+set)

#3166. [Heoi2013]Alo

BZOJ3166

description

Welcome to ALO ( Arithmetic and Logistic Online)。这是一个VR MMORPG ,
如名字所见,到处充满了数学的谜题。
现在你拥有n颗宝石,每颗宝石有一个能量密度,记为ai,这些宝石的能量密度两两不同。现在你可以选取连续的一些宝石(必须多于一个)进行融合,设为 ai, ai+1, …, a j,则融合而成的宝石的能量密度为这些宝石中能量密度的次大值与其他任意一颗宝石的能量密度按位异或的值,即,设该段宝石能量密度次大值为k,则生成的宝石的能量密度为max{k xor ap | ap ≠ k , i ≤ p ≤ j}。
现在你需要知道你怎么选取需要融合的宝石,才能使生成的宝石能量密度最大。

Input

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

Output

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

Sample Input

5 
9 2 1 4 7

Sample Output

14

Hint

选择区间[1,5],最大值为 7 xor 9。

对于 100%的数据有 1 ≤ n ≤ 50000, 0 ≤ ai ≤ 10^9

solution

首先肯定是枚举每一个宝石的 a i a_i ai作为次大值

区间 [ l , r ] [l,r] [l,r]内与 x x x异或的最大值,就是可持久化trie树

对于 i i i版本的trie树记录的是 [ 1 , i ] [1,i] [1,i]的所有宝石信息

但是本题的难点是在于求出每个宝石做次大值时,有效的区间范围

定义前驱的含义:宝石密度大于 a i a_i ai的在 i i i前面的距 i i i最近的宝石

定义后继的含义:宝石密度大于 a i a_i ai的在 i i i后面的距 i i i最近的宝石

对于每个 i i i,询问的合法区间其实是前驱的前驱位置l加一,后继的后继位置r减一

  • 这里就有问题了,看似好像包含了两个比 a i a_i ai大的宝石, i i i宝石不再是次大值宝石

    • 其实并不是,要求包含前驱后继并不是意味着在 ( l , r ) (l,r) (l,r)这个区间进行询问

      这个区间当然 i i i宝石是次次大值

    • 这是意味着两个独立区间的问题 ( l , i ] ; [ i , r ) (l,i];[i,r) (l,i];[i,r)

      这么说的原因是,假设 a i ⨁ a j ( l < j < i < r ) a_i\bigoplus a_j(l<j<i<r) aiaj(l<j<i<r)是最大值,那么相当于选择的区间是 ( l , i ] (l,i] (l,i]

      同理假设 a i ⨁ a j ( l < i < j < r ) a_i\bigoplus a_j(l<i<j<r) aiaj(l<i<j<r)是最大值,那么相当于选择的区间是 [ i , r ) [i,r) [i,r)

      这样 a i a_i ai在两个区间就扮演着次大值的角色了

    • 如果只是对于一个 i i i考虑前驱的位置加一到后继的后继位置减一,其实只是考虑了最大值在次大值 a i a_i ai后面的区间,忽略了最大值在 a i a_i ai前面的区间

    • 如果只是对于一个 i i i考虑前驱的前驱位置加一到后继的位置减一,其实只是考虑了最大值在次大值 a i a_i ai前面的区间,忽略了最大值在 a i a_i ai后面的区间

知道了选取区间端点的条件,代码怎么找呢?

  • set

具体而言:将宝石按照密度排序,按密度从大到小地加入宝石,set里面放的是宝石的下标 i i i,那么此时set里面的所有宝石都大于等于现在这个刚加进去的宝石密度

为了能阐释得更清楚,我们直接解读代码

首先是寻找后继的后继的位置,减一会在主函数的查询调用时减掉,这里暂时不考虑

  • 这里两个if语句的先后顺序是先让迭代器it自加后,再判断是否指向了setend()位置

  • 众所周知set是左闭右开的,end()位置是最后一个值的位置的下一个空位置

  • 先自加一,指向后继;再自加一,指向后继的后继

  • int find_r( int x ) {
    	auto it = s.find( x );
    	if( ++ it == s.end() ) return n + 1;
    	if( ++ it == s.end() ) return n + 1;
    	return *it;
    }
    

然后看寻找前驱的前驱代码,同样的加一操作在主函数的询问完成,这里不进行加一

  • 这里两个if语句的先后顺序是,先判断是否指向了begin()位置,再自减一

  • 第一个if如果成立,意味着当前加入的宝石下标最小,前驱都没有

  • 否则,迭代器就指向了前驱

  • 再到第二个if语句,如果成立,意味着前驱就是下标最小的宝石了,前驱的前驱没有

  • 否则迭代器就指向了前驱的前驱,达到目的

int find_l( int x ) {
	auto it = s.find( x );
	if( it -- == s.begin() ) return 0;
	if( it -- == s.begin() ) return 0;
	return *it;
}

最后还有一个代码细节

在插入操作和查询操作中,涉及到最后一层二进制位 0 0 0的处理

void insert( int &now, int lst, int x, int d ) {
	if( d < 0 ) return;
	t[now = ++ cnt] = t[lst];
	t[now].sum ++;
	int k = x >> d & 1;
	insert( t[now].son[k], t[lst].son[k], x, d - 1 );
}

int query( int l, int r, int x, int d ) {
	if( t[r].sum - t[l].sum == 0 ) return 0;
	if( d == 0 ) return 1;
    int k = x >> d & 1;
	if( t[t[r].son[k ^ 1]].sum - t[t[l].son[k ^ 1]].sum )
		return query( t[l].son[k ^ 1], t[r].son[k ^ 1], x, d - 1 ) + ( 1 << d );
	else
		return query( t[l].son[k], t[r].son[k], x, d - 1 );
}

如果不在query操作设置if( d == 0 ) return 1的这个出口,在insert的时候就必须把二进制位 − 1 -1 1建出来,即

void insert( int &now, int lst, int x, int d ) {
	t[now = ++ cnt] = t[lst];
	t[now].sum ++;
    if( d < 0 ) return;
	int k = x >> d & 1;
	insert( t[now].son[k], t[lst].son[k], x, d - 1 );
}

int query( int l, int r, int x, int d ) {
    if( d < 0 ) return 0;
	if( t[r].sum - t[l].sum == 0 ) return 0;
	int k = x >> d & 1;
	if( t[t[r].son[k ^ 1]].sum - t[t[l].son[k ^ 1]].sum )
		return query( t[l].son[k ^ 1], t[r].son[k ^ 1], x, d - 1 ) + ( 1 << d );
	else
		return query( t[l].son[k], t[r].son[k], x, d - 1 );
}

为什么呢?

  • 如果不在查询时设置最底层叶子的出口,那么就会进入对叶子左右儿子的if判断
  • 然而,插入操作又只在最底层叶子建点后就返回了
  • 所以叶子节点的儿子没有被分配过,贸然访问,完全不知道计算机会找到哪儿去
  • 我就是这个顺序细节问题,一直比答案少一

code

#include <set>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 50005
set < int > s;
pair < int, int > a[maxn];
int n, cnt;
int root[maxn];
struct { int son[2], sum; } t[maxn * 35];

void insert( int &now, int lst, int x, int d ) {
	if( d < 0 ) return;
	t[now = ++ cnt] = t[lst];
	t[now].sum ++;
	int k = x >> d & 1;
	insert( t[now].son[k], t[lst].son[k], x, d - 1 );
}

int query( int l, int r, int x, int d ) {
	if( t[r].sum - t[l].sum == 0 ) return 0;
	if( d == 0 ) return 1;
	int k = x >> d & 1;
	if( t[t[r].son[k ^ 1]].sum - t[t[l].son[k ^ 1]].sum )
		return query( t[l].son[k ^ 1], t[r].son[k ^ 1], x, d - 1 ) + ( 1 << d );
	else
		return query( t[l].son[k], t[r].son[k], x, d - 1 );
}

int find_l( int x ) {
	auto it = s.find( x );
	if( it -- == s.begin() ) return 0;
	if( it -- == s.begin() ) return 0;
	return *it;
}

int find_r( int x ) {
	auto it = s.find( x );
	if( ++ it == s.end() ) return n + 1;
	if( ++ it == s.end() ) return n + 1;
	return *it;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &a[i].first );
		a[i].second = i;
		insert( root[i], root[i - 1], a[i].first, 30 );
	}
	sort( a + 1, a + n + 1 );
	int ans = 0;
	s.insert( a[n].second );
	for( int i = n - 1;i;i -- ) {
		s.insert( a[i].second );
		int l = find_l( a[i].second );
		int r = find_r( a[i].second );
		ans = max( ans, query( root[l], root[r - 1], a[i].first, 30 ) );
	}
	printf( "%d\n", ans );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值