usaco training 6.1 奶牛异或 (字典树)

题面

在这里插入图片描述

输入样例

5
1
0
5
4
2

输出样例

6 4 5

在这里插入图片描述

题解

  1. 区间异或找最大,一看就是01字典树的变型,我们的板子点这里是找两个数的最大异或,此题是让找一个区间的异或和,那么我们就可以用前缀和的思想去做
  2. 用s数组表示前i个数的异或,s[l-1]^s[r] = al ^ al+1 ^ al+2 ^ … ^ar ,这样就可以求出任意一段连续区间的异或了
  3. 条件是让满足区间异或最大而且让r尽可能的小,那么我们就从左向右遍历,每次右端点增加一位(也就是区间长度+1),看在这个区间的最大异或是多少,如果大于就更新答案(等于是不更新的,因为要保证r尽可能的小)
  4. 还有一个条件是:如果仍然存在多个可选的序列,那么选择长度最短的那个序列。意思就是让 l 尽可能的大 ,我们只需要在插入的时候,将前面相同的值用后面的值覆盖掉就好,这样每次输出的一定是后面的值。
  5. 还没听懂?举个例子,假设s[3] =5 ,s[5]=5 ,s[7]=9, 假设在满足前两个条件下,按题中要求肯定是要s[7] ^ s[5] , 那么我们怎样避免s[3], 就是在插入的时候,我们会记录每个点的下标,对于 5 这个数,在字典树中插入的肯定是两次,从前到后依次插入,第一次插入5,记录下标为3,第二次插入5,发现已经存在,那么就不用新开一个节点,直接将5这个节点的下标改为5即可

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 1e5 + 10;
const int M = N * 21;
int n, x;
int s[N], id[M];
int tr[M][2], idx;

void insert(int x, int k) {
    
    
    int p = 0;
    for (int i = 20; i >= 0; i--) {
    
    
        int u = x >> i & 1;
        if (!tr[p][u]) tr[p][u] = ++idx;
        p = tr[p][u];
    }
    id[p] = k;  //记录下标,覆盖操作
}

int query(int x) {
    
    
    int p = 0;
    for (int i = 20; i >= 0; i--) {
    
    
        int u = x >> i & 1;
        if (tr[p][u ^ 1]) p = tr[p][u ^ 1];
        else p = tr[p][u];
    }
    return id[p];
}

int main() {
    
    

    cin >> n;
    insert(s[0], 0);
    int res = -1;
    int l, r;
    for (int i = 1; i <= n; i++) {
    
    
        cin >> x;
        s[i] = s[i - 1] ^ x;
        int k = query(s[i]);
        int maxn = s[i] ^s[k];
        if (maxn > res) {
    
    
            res = maxn;
            l = k + 1, r = i;
        }
        insert(s[i], i);
    }
    cout << res << " " << l << " " << r << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44791484/article/details/113815094