最大异或和

给定一个非负整数数列 a a a,初始长度为 N N N
请在所有长度不超过 M M M连续子数组中,找出子数组异或和的最大值。
子数组的异或和即为子数组中所有元素按位异或得到的结果。
注意:子数组可以为空。
输入格式
第一行包含两个整数 N , M 。 N,M。 N,M
第二行包含 W W W个整数,其中第i个为 a i a_i ai
输出格式
输出可以得到的子数组异或和的最大值。
数据范围
对于20%的数据,1≤M≤W≤100
对于50%的数据,1≤M≤W≤1000
对于100%的数据,1≤M≤N≤105,0≤a,≤231-1
输入样例:

3 2
1 2 4

输出样例:

6

分析:
要求连续的子数组中的最大异或和,首先想想怎么遍历连续的子数组,这个时候考虑用前缀异或和
比如sum[i] = a 1 a_1 a1 ^ a 2 a_2 a2 ^ a 3 a_3 a3……^ a i a_i ai,要求区间[l,r]的子数组那就等于sum[r] ^ sum[l-1];
因为sum[r] = a 1 a_1 a1 ^ a 2 a_2 a2 ^ a 3 a_3 a3……^ a r a_r ar,sum[l-1] = a 1 a_1 a1 ^ a 2 a_2 a2 ^ a 3 a_3 a3……^ a l − 1 a_{l-1} al1,由于异或的性质a^ a = 0,0 ^ a = a,故前面l-1个数全部变为0,从l开始到r开始异或。接着利用贪心的思想,枚举前缀异或和,我们希望与这个前缀异或和的每一位都不同,那么异或之后那一位就是1,这样能使异或之后尽可能大。那么怎么存储这些位数呢,用trie树,字典树被用来高效地存储和查询异或前缀和。每一层存储对应位数是1还是0;再用一个cn数组来维护数组最大长度为M。
下面是完整代码

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

using namespace std;

const int N = 1e5 + 10; // 数列长度上限
const int M = 31 * N; // 字典树节点最多有N*31个
int p[M][2], ct[M], idx; // 字典树、计数数组和节点索引

int sum[N]; // 存储前缀异或和

// 初始化字典树和全局变量
void init() {
    memset(p, 0, sizeof(p)); // 初始化字典树的所有指针为0
    memset(ct, 0, sizeof(ct)); // 初始化计数数组为0
    idx = 1; // 节点索引从1开始,因为0已经被用作根节点
}

// 插入或删除一个前缀和到字典树中
void insertt(int u, int c) {
    int t = 0; // 字典树的根节点起始索引

    for (int i = 30; i >= 0; i--) { // 对于每一位
        int x = u >> i & 1; // 取出当前位的值(0或1)
        if (!p[t][x]) { // 如果路径不存在,则创建一个新节点
            p[t][x] = ++idx;
        }
        t = p[t][x]; // 移动到下一个节点
        ct[t] += c; // 更新计数,c为1表示插入,为-1表示删除
    }
}

// 查询与给定前缀和异或值最大的数
int query(int u) {
    int t = 0;
    int res = 0;

    for (int i = 30; i >= 0; i--) { // 从最高位到最低位
        int x = u >> i & 1; // 获取当前位
        int want = x ? 0 : 1; // 获取我们想要的相反的位

        // 如果相反的位存在,并且当前滑动窗口内有这样的数
        if (ct[p[t][want]] > 0) {
            res |= (want << i); // 设置结果的当前位为1
            t = p[t][want]; // 沿着这个分支向下
        } else {
            res |= (x << i); // 设置结果的当前位保持不变
            t = p[t][x]; // 沿着这个分支向下
        }
    }

    return res ^ u; // 返回最大异或和
    //这个res就是某个前缀,异或之后必能取到某个连续的数组,比如最大异或和数组是下标为4,5这两个异或
    //那么当i等于5的时候,查找长度为M的子数组时,可以找到最优的前缀异或和sum[3],在和sum[5]异或之后
    //就得到了结果,更具体一点:res 是根据与 u (当前的前缀和)异或后能得到最大值的前缀和构建的。
    //res 的每一位都是通过字典树搜索得到的,并且尽可能与 u 中的对应位相反(通过选择相反位的路径),从而使得 u XOR res 的结果尽可能大。
}

int main() {
    init(); // 初始化字典树和相关变量
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &sum[i]);
        sum[i] ^= sum[i - 1]; // 计算前缀异或和
    }

    insertt(0, 1); // 插入0,处理空子数组的情况
    int res = 0; // 存储最大异或和

    for (int i = 1; i <= n; i++) {
        if (i > m) {
            insertt(sum[i - m - 1], -1); // 当前滑动窗口的前一个数,从字典树中删除
        }
        res = max(res, query(sum[i])); // 在滑动窗口内查询当前前缀和的最大异或结果
        insertt(sum[i], 1); // 插入当前前缀和到字典树中
    }

    printf("%d\n", res); // 输出最大异或和
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值