#3166. [Heoi2013]Alo
- description
- solution
- code
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
首先肯定是枚举每一个宝石的aia_iai作为次大值
区间[l,r][l,r][l,r]内与xxx异或的最大值,就是可持久化trie树
对于iii版本的trie树
记录的是[1,i][1,i][1,i]的所有宝石信息
但是本题的难点是在于求出每个宝石做次大值时,有效的区间范围
定义前驱的含义:宝石密度大于aia_iai的在iii前面的距iii最近的宝石
定义后继的含义:宝石密度大于aia_iai的在iii后面的距iii最近的宝石
对于每个iii,询问的合法区间其实是前驱的前驱位置l加一,后继的后继位置r减一
-
这里就有问题了,看似好像包含了两个比aia_iai大的宝石,iii宝石不再是次大值宝石
-
其实并不是,要求包含前驱后继并不是意味着在(l,r)(l,r)(l,r)这个区间进行询问
这个区间当然iii宝石是次次大值
-
这是意味着两个独立区间的问题(l,i];[i,r)(l,i];[i,r)(l,i];[i,r)
这么说的原因是,假设ai⨁aj(l<j<i<r)a_i\bigoplus a_j(l<j<i<r)ai⨁aj(l<j<i<r)是最大值,那么相当于选择的区间是(l,i](l,i](l,i]。
同理假设ai⨁aj(l<i<j<r)a_i\bigoplus a_j(l<i<j<r)ai⨁aj(l<i<j<r)是最大值,那么相当于选择的区间是[i,r)[i,r)[i,r)
这样aia_iai在两个区间就扮演着次大值的角色了
-
如果只是对于一个iii考虑前驱的位置加一到后继的后继位置减一,其实只是考虑了最大值在次大值aia_iai后面的区间,忽略了最大值在aia_iai前面的区间
-
如果只是对于一个iii考虑前驱的前驱位置加一到后继的位置减一,其实只是考虑了最大值在次大值aia_iai前面的区间,忽略了最大值在aia_iai后面的区间
-
知道了选取区间端点的条件,代码怎么找呢?
set
具体而言:将宝石按照密度排序,按密度从大到小地加入宝石,set
里面放的是宝石的下标iii,那么此时set
里面的所有宝石都大于等于现在这个刚加进去的宝石密度
为了能阐释得更清楚,我们直接解读代码
首先是寻找后继的后继的位置,减一会在主函数的查询调用时减掉,这里暂时不考虑
-
这里两个
if
语句的先后顺序是先让迭代器it
自加后,再判断是否指向了set
的end()
位置 -
众所周知
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;
}
最后还有一个代码细节
在插入操作和查询操作中,涉及到最后一层二进制位000的处理
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 );elsereturn 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 );elsereturn 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 );elsereturn 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;
}