最大异或和 Trie 字典树

题目:

给定一个非负整数数列 a,初始长度为 N。

请在所有长度不超过 M 的连续子数组中,找出子数组异或和的最大值。

子数组的异或和即为子数组中所有元素按位异或得到的结果。

注意:子数组可以为空。

输入格式

第一行包含两个整数 N,M,。

第二行包含 N个整数,其中第 i个为 ai。

输出格式

输出可以得到的子数组异或和的最大值。

数据范围

对于 20% 的数据,1≤M≤N≤100
对于 50% 的数据,1≤M≤N≤1000
对于 100% 的数据,1≤M≤N≤1e5,0≤ai≤2^31−1

输入样例:

3 2
1 2 4

输出样例:

6


思路:

纯暴力的话也就是用两个指针从左到右遍历过去,时间复杂度是O(n^3)

一、异或前缀和

首先像这种在一个大数组中求一个子数组怎么怎么样的,抽象的说也就是在一个大段里求一个小段的,我们一般都会用到前缀和,这一题也是如此的,前提是我们要知道异或的自反性
例如:arr=【5,1,2,4,5,6】 的异或前缀和为:brr=[5, 4, 6, 2, 7, 1],如果我们想知道 arr[2]^arr[3]^arr[4] 可以直接使用 brr[4]^brr[1]就可以得到,不信的同学可以试试

此时我们想要求最大异或和,依然是两个指针从左到右,不过省去了其中的从左指针到右指针,所以是O(n^2)

二、01字典树

思考后我们发现,我们现在的题变成了,在异或前缀和数组中所有长度为m的段中选出异或结果最大的一对,当然这一对肯定是在同一段的,而异或也就是转为二进制后进行 同位为0 异位为1 。

首先,我们将所有的数字看成31位的二进制数,我们可以用它来建立字典树,我们可以将左分支看成0,右分支看成1,然后将元素一位一位的放入我们的字典树,查找的时候也是一位位的看,每次尽量进入不同位的分支,因为不同才为1
我们的字典树有三个操作,添加删除和查找,这三个操作都是要从高位往低位看的为什么呢?
因为高位得出的1要比低位得出的1的价值要高,如果在查找的时候我们从低位向高位看,进入了某分支后,该分支的高位并没有我们要的不同位,那就得不偿失了,而从高位向低位看,就算从高位进入的分支在低位没有我们要的不同位,那我们也得到了相比较而言更大的结果

代码实现:

############2023.6.2
class Tree:#左为0,右为1
    root=None#根
    def __init__(self,v=1):
        self.left=None
        self.right=None
        self.v=v
    def add_L(self,x):
        if self.left:
            self.left.v+=x
        else:
            self.left=Tree(x)
        if self.left.v==0:
            self.left=None

    def add_R(self,x):
        if self.right:
            self.right.v+=x
        else:
            self.right=Tree(x)
        if self.right.v==0:
            self.right=None
Tree.root=Tree()

def Set(a,x=1):#添加时x为1,删除时为-1
    r=Tree.root
    for i in range(30,-1,-1):
        s=a>>i&1
        if s:
            v=r.add_R(x)
            r=r.right
        else:
            v=r.add_L(x)
            r=r.left
        if not r:#已经断了,用在减数当中
            break

def find(a):
    r=Tree.root
    ans=0
    for i in range(30,-1,-1):
        s=a>>i&1
        if s and r.left:
            r=r.left
            ans+=1<<i
        elif not s and r.right:
            r=r.right
            ans += 1 <<i
        else:#实在不行随便进
            if r.left:
                r=r.left
            elif r.right:
                r=r.right
            elif s:#没得进就按自己来
                ans+=1<<i
    return ans

n,m=map(int,input().split())
arr=list(map(int,input().split()))
for i in range(1,n):
    arr[i]^=arr[i-1]

Set(arr[0])
ans=arr[0]

for i in range(1,n):
    if i>m:
        Set(arr[i - m-1], -1)
    elif i<m:#在i<=m里有一种不选的情况
        ans=max(ans,arr[i])
    ans=max(find(arr[i]),ans)
    Set(arr[i])
print(ans)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自 在

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值