宇宙人浇花(异或前缀和+trie+思维,最大异或对问题)

简介: 宇宙人浇花(异或前缀和+trie+思维,最大异或对问题)

题目传送门

1.题目大意:

a序列的n个数异或和为:a 1 a1a1 xor a 2 a2a2 xor a 3 a3a3 xor a 4 a4a4…xor a n anan

可以对一段连续的a l . . a r al..aral..ar加一,操作一次或者不操作,求操作之后的n个数的异或和

2.分析题目:

最后一定是前面一部分不加一,中间一部分加一,后面一部分不加一,注意每部分可能长度为0

维护两个前缀数组:

1.b [ i ] 维 护 前 i 个 a [ i ] + 1 的 异 或 和 b[i]维护前i个a[i]+1的异或和b[i]ia[i]+1

2.a [ i ] 维 护 前 i 个 a [ i ] 的 异 或 和 a[i]维护前i个a[i]的异或和a[i]ia[i]

3.再用字典树维护a[n] ^ a[i] ^ b[i]

为什么要字典树维护a[n]^a[i] ^b[i]呢?

想想,首先异或运算是满足交换律和结合律的

因为a [ i ] a[i]a[i]维护的是原a数组的前缀异或和,a[i]^a[n]剩下的是原a数组的,a[i+1] ^ a[i+2]a[n]

b[i] ^ a[i] ^ a[n]为 原a数组的前i个(a[i]+1)异或和再异或原 数组的后面的n-i个a[i]的异或和

(a[1]+1) ^ (a[2]+1) ^(a[3]+1) ^ … ^(a[i]+1) ^a[i+1] ^a[i+2] ^… ^ a[n]

遍历一遍将所有的a[i]^a[n] ^b[i]放trie上面

4.再用a[i]^b[i]去查询trie上的最大异或对

为什么是a[i]^b[i]去查询呢?

因为答案一定是某一个 a[x] ^ b[x] ^ (a[n] ^ a[i] ^ b[i])

简单证明一下:

通过3已知,(a[n] ^ a[i] ^ b[i])为:

(a[1]+1) ^ (a[2]+1) ^(a[3]+1) ^ … ^(a[i]+1) ^a[i+1] ^a[i+2] ^… ^ a[n]

(这里的a数组是没有前缀处理的原a数组)

此时a[n] ^ a[i] ^ b[i]再异或b[x]^a[x]

就会将索引为1-x的异或变成0((a[i]+1)^(a[i]+1)),再将索引1-x的异或和变成a[1] ^ a[2] a[x]

整个a[x] ^ b[x] ^ (a[n] ^ a[i] ^ b[i])变成:

a[1] ^ a[2] ^ …^ a[x] ^ (a[x+1]+1) ^ (a[x+1]+1) ^ (a[x+2]+1) (a[x+k]+1) ^ a[x+k+1] ^ …a[n]

分成开始说的三段!

接下来就是求最大异或对了!

代码:

#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
# define rep(i,be,en) for(int i=be;i<=en;i++)
# define pre(i,be,en) for(int i=be;i>=en;i--)
typedef pair<int, int> PII;
#define ll long long
#define endl "\n"
#define LOCAL
#define pb push_back
const int N = 2e5 + 10, INF = 0x3f3f3f3f;
inline int read()
{
  int X = 0;
  bool flag = 1;
  char ch = getchar();
  while (ch < '0' || ch > '9') {
    if (ch == '-') flag = 0;
    ch = getchar();
  }
  while (ch >= '0' && ch <= '9') {
    X = (X << 1) + (X << 3) + ch - '0';
    ch = getchar();
  }
  if (flag) return X;
  return ~(X - 1);
}
inline void write(int X)
{
  if (X < 0) {
    X = ~(X - 1);
    putchar('-');
  }
  if (X > 9) write(X / 10);
  putchar(X % 10 + '0');
}
int son[N][3];
int n;
int idx, a[N], b[N];
void insert(int x)
{
  int p = 0, u;
  for (int i = 31; i >= 0; i--)
  {
    u = (x >> i) & 1;
    if (!son[p][u]) son[p][u] = ++idx;
    p = son[p][u];
  }
}
int query(int x)
{
  int p = 0, u;
  int res = 0;
  for (int i = 31; i >= 0; i--)
  {
    u = (x >> i) & 1;
    if (son[p][u ^ 1]) res += 1 << i, p = son[p][u ^ 1];
    else
      p = son[p][u];
  }
  return res;
}
void solve()
{
  n = read();
  rep(i, 1, n)
  {
    a[i] = read();
    b[i] = a[i] + 1;
  }
  for (int i = 1; i <= n; i++)
  {
    a[i] ^= a[i - 1];
    b[i] ^= b[i - 1];
  }
  for (int i = 0; i <= n; i++)
  {
    int x = a[n] ^ a[i] ^ b[i];
    insert(x);
  }
  int ans = 0;
  for (int i = 0; i <= n; i++)
    ans = max(ans, query(b[i] ^ a[i]));
  cout << ans << "\n";
}
int main() {
  ios::sync_with_stdio;
  cin.tie(0);
  cout.tie(0);
  //#ifdef LOCAL
  //freopen("data.in.txt","r",stdin);
  //freopen("data.out.txt","w",stdout);
  //#endif
  int T = 1;
  //scanf("%d",&T);
  while (T--)
  {
    solve();
  }
  return 0;
}

撰文不易,点个赞再走吧!

目录
相关文章
|
6月前
|
算法
简记二分算法模板与代码案例:整数二分和浮点数二分
本文介绍了两种算法模板,分别是整数二分和浮点数二分。
48 0
|
6月前
|
算法 测试技术 C++
【数学归纳法】【位运算】【异或】3068最大节点价值之和
【数学归纳法】【位运算】【异或】3068最大节点价值之和
|
算法
基础算法(贪心 合并 位运算)
基础算法(贪心 合并 位运算)
60 0
|
6月前
|
测试技术
【数学】【记忆化搜索 】【动态规划】964. 表示数字的最少运算符
【数学】【记忆化搜索 】【动态规划】964. 表示数字的最少运算符
|
C语言
next数组的两种求法详解及完整代码
求字符串的next数组: 方法一: 这里我们将next数组第1,2位分别设为0,1(还有-1,0这种设法,这里先将其设为0,1若有需要再减一即可) 后面求解每一位的next值时,根据前一位进行比较。 从第三位开始,将前一位与其next值对应的内容进行比较, 如果相等,则该位的next值就是前一位的next值加上1; 如果不等,向前继续寻找next值对应的内容来与前一位进行比较, 直到找到某个位上内容的next值对应的内容与前一位相等为止, 则这个位对应的值加上1即为需求的next值; 如果找到第一位都没有
366 0
next数组的两种求法详解及完整代码
|
11月前
|
存储 算法
【LeetCode】每日一题&最后一个单词的长度&投票法求解多数元素&异或操作符巧解只出现一次的数字&整数反转
【LeetCode】每日一题&最后一个单词的长度&投票法求解多数元素&异或操作符巧解只出现一次的数字&整数反转
|
算法
next数组(详细求法)
next数组(详细求法)
184 0
|
Python
深入理解动态规划算法 | 凑整数
深入理解动态规划算法 | 凑整数
129 0
|
机器学习/深度学习 算法 NoSQL
【基础算法】浅浅刷个小题 # 反转字符串 # 反转字符串 II # 三个数的最大乘积 #
【基础算法】浅浅刷个小题 # 反转字符串 # 反转字符串 II # 三个数的最大乘积 #
|
算法 C++
基础算法-整数二分
二分法的基本思想比较简单,是用来在数组当中查找特定元素的算法。 二分可以分为整数二分和浮点二分,本文主要介绍整数二分。