算法题——最大异或和

题目

代码

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 3100000+10;
int n,m;
int trie[N][2],cnt[N];//cnt为以该节点为根节点的树里有多少个叶子节点
int idx;
int s[N];//存放前缀异或和
void insert(int x, int v)//插入时v为1,删除时v为-1
{
    int p=0;
    for(int i=30; i>=0; --i)//a最大值为2的31次方减1,把a表示为二进制,只有31位,右移0~30位再&1就可以得到a的二进制的每一位,从i=30开始是为了从a的高位开始录入字典树
    {
        int u=x>>i&1;//右移i位,&1是因为int有32位,&1去掉前31位
        if(!trie[p][u]) trie[p][u]=++idx;
        p=trie[p][u];
        cnt[p]+=v;
    }
}
int query(int x) //查询trie中与x异或和的最大值
{
    int res=0, p=0;//异或和
    for(int i=30; i>=0; --i)
    {
        int u=x>>i&1;
        if(cnt[trie[p][!u]]) p=trie[p][!u], res=res*2+1;
        else p=trie[p][u], res*=2;
    }
    return res;
}
int main()
{
    cin>>n>>m;
    for(int i=1; i<=n; ++i)
    {
        int x;
        cin>>x;
        s[i]=s[i-1]^x;//前缀和
    }
    int res=0;
    insert(s[0],1);//空数组,结果为0
    for(int i=1; i<=n; ++i)
    {
        if(i-m-1>=0) insert(s[i-m-1],-1);//滑动窗口,向右滑动,删除上一次的左端起点。这里的左端起点是L-1,上一次的左端起点是L-2,L>=R-m+1,则删除的是R-m-1
        res = max(res, query(s[i]));
        insert(s[i], 1);
    }
    cout<<res<<endl;
    return 0;
}

/*
先计算出前缀异或和,m长度的异或和为s[r]^s[l-1],其中r-l+1=m
利用滑动窗口和字典树,窗口移动到哪一段,字典树中只保存s[l-1]到s[r]的值,不满足可以计算出一段小于等于m长度异或和的前缀异或和都被删除了
确定右端点,用query函数,以右端点确定的s[r]与字典树中的前缀异或和进行计算
u=x>>i&1,就是从s[r]的高位开始取出每一位和字典树中的节点进行异或
想要异或和最大,我们知道异或时同为0异为1,因此我们找到一个前缀异或和,保证它的每一个与s[r]对应的位跟s[r]不同,也就是和u不同即可
因此我们去找trie[p][!u]是否存在,如果存在,那么当前结果res左移一位再加上异或和的结果1,也就是res=res*2+1
如果trie[p][!u]不存在,那么就只有trie[p][u],这一位跟s[r]的对应位是相同的,异或结果为0,直接把res左移一位即可
*/

posted @ 2023-03-10 19:25  HD0117  阅读(22)  评论(0编辑  收藏  举报