题目:
给定一个非负整数数列 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)