P2134【Codeforces Round #172 (Div. 1)】“最大”异或“次大”
时间限制 : 20000 MS 空间限制 : 265536 KB
问题描述
bike喜欢查找数列中的第二大的元素(数列中的次大元素值应该严格小于最大的元素值)。
一个正整数数列 x1, x2, …, xk (k > 1)的“幸运数字”的值就是该数列中最大元素与次大元素相异或(XOR)的值。
给你一个正整数数列s1, s2, …, sn (n > 1)
我们定义子序列sl, sl + 1, …, sr as s[l..r] (1 ≤ l < r ≤ n),
你的任务是找出所有子序列中最大的那个”幸运数字”
输入格式
第一行, 一个整数n(1 < n ≤ 10^5)
第二行,n个空格间隔的整数s1, s2, …, sn (1 ≤ si ≤ 10^9).
输出格式
一个整数,表示所求最大的幸运数字。
样例输入
样例1:
5
5 2 1 4 3
样例2:
5
9 8 3 5 7
样例输出
样例1:
7
样例2:
15
提示
样例1说明:
s数列有s[1..5],s[1..4],…,s[2..5],s[2..4],…,s[3,5]……等子序列,其中子序列s[4..5] = {4, 3} 的“幸运数字”为(4 xor 3)=7,是最大的一个, 你也可以选择s[1..2]
样例2说明:
子序列s[2..5]={8,3,5,7}的幸运数字最大为15
[解析]
首先,数据范围在10^5,所以时间复杂度小于n^2,所以暴搜是不行的
题目正解为 单调队列
解析:题目中要求找到的两个数是最大和次大数,这道题目的关键在于次大数
通过单调递减队列我们就可以知道当前数字分别作为最大数和次大数的情况
如 5 1 3 2 4 中的 3
在3入队列之前队列中有5 1
因为我们要单调递减队列,所以1要被pop掉,但因为3比1大,所以在1到3这个区间当中,1,3分别是次大数和最大数,所以对此进行一次异或运算,然后把1pop掉
然后碰到5比3大,那么我们就找到了3左边第一个比3大的数,那么这就是从第一个数到3为止唯一一次3可以作为次大数的情况
假设5左边还有比5大的数,那么那个时候3最起码是第三大的数,无法参与运算
假设5左边有比5小的数x,那么因为是递减队列,x一定已经被讨论过了,就算没有,那么要么x无法参与5和3的运算(x<3),要么3无法参与5和x的运算(x>3)
当3的右边出现比它大的数的时候,3就会参与第二次作为次大数的运算
这就是一个数字进入队列后的讨论
综上,我们还可以得出一个数x参与讨论的范围是从左边第一个不小于它的数到右边第一个不小于它的数(x为第一个数、最后一个数、所有数字中最大的数和次大的数的情况并不符合这个结论,但无伤大雅,稍加思考就可以得出这些特殊的情况的处理方式),所以被pop掉的数可以不用顾虑它会需要参与之后加入的数字的运算的情况,因为在它入队之前就已经讨论了从它左边第一个比它大的数到它为止的所有情况,当它被pop掉时,它就遇到了它右边第一个比它大的数,至此它就已经在它的讨论范围内结束了所有的讨论。
附上代码
#include <iostream>
#include <cstdio>
#include <deque>
#include <algorithm>
using namespace std;
int y,n,nums[123456],x,res=0;
deque<int>going;
inline int G(){
char t=getchar(); int o,f=0;
while(t<48||t>57)f|=(t=='-'),t=getchar();
for(o=0;t>47&&t<58;t=getchar())o=(o<<1)+(o<<3)+t-48;
return f?-o:o;
}//输入优化
int main()
{
n=G();
for(int i=1;i<=n;i++)nums[i]=G();
going.push_back(nums[1]);
for(int i=2;i<=n;i++)
{
x=nums[i];
while(going.size()&&going.back()<x)
{
y=going.back()^x;
if(y>res)res=y;
going.pop_back();
}
if(going.size()){y=x^going.back();if(y>res)res=y;}
going.push_back(x);
}
printf("%d",res);
return 0;
}