E. Bored Bakry
题意:
给n个数字,求最长连续的子序列,要求子序列中每个数字的相与和大于 异或和。
分析:
① 长度必须为偶数,才有可能与和 大于 异或和。
证明①: 与为1 那么必须全为1, 当有奇数个的时候,全为一的异或也是1, 所以奇数最好的情况是相等。
② 如果对序列求前缀异或,当有两个异或值相同的时候,表面这两个数之间数的异或为0(包含最后一个数字)。
证明②: 异或是自己的逆运算。
③ 当最高位大于的时候, 后面的值都是无所谓的。
证明③: 1000 > 0999
所以: 我们需要对每一位求前缀异或和, 如果出现一样的异或值,并且奇偶性质相同, 那么我们就可以更新一下答案。枚举每一位,并且枚举每个数 时间复杂度是 O(20n) 基本logn
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6+10;
int n;
int a[N], c[N];
vector<int> save[N]; // 用来保存前缀异或的下标
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
int ans = 0;
for(int d = 0; d <= 20; d ++)
{
save[0].push_back(0); // 如果出现异或值为0, 那么就可以与起始情况对应
int pre = 0, j = 0;
for(int i = 1; i <= n; i ++)
{
pre ^= (a[i] >> (d+1)); // 从后往前异或 或者从前往后都是可以的。
c[i] = pre; // 记录当前的前缀异或和值
if((a[i] >> d)&1) // 如果当前位为1 的情况才有可能更新答案
{
if(save[c[i]].size() < 2) // 如果小于2,那就代表不能更新答案
{
if(save[pre].empty()) save[pre].push_back(i); // 如果空 直接放入
else if((save[pre][0]^i)&1) save[pre].push_back(i); // 否则判断一下与第一个奇偶性质是否相反
}
for(auto t : save[pre]) // 遍历每个保存的
{
if(((t^i)&1) == 0) // 如果保存的值奇偶性质相反,那么就可以更新结果
ans = max(ans, i-t);
}
}
else // 当前位为0, 那么就将前面存储的全部清空,因为答案不可以跨越这个位置
{
while(j < i) // 清空当前存储
{
if(j == 0) save[0].clear();
save[c[j]].clear();
j ++;
}
save[c[i]].push_back(i); // 当前位置存入
}
}
}
cout << ans << endl;
return 0;
}