牛异或(区间异或)
题目地址:https://www.acwing.com/problem/content/description/1416/
题目大意:就是找到一个区间满足\([l,r]\)异或和最大,且\(r\)端点尽可能小的情况下保证\((r-l+1)\)最小。
显然是字典树,这个不需要考虑,但是在具体实现过程还是磕磕绊绊的,说明代码能力还是不太行。
思路如下:
根据异或和的性质,可以很简单的推导出\(s[r]\)^\(s[l-1]=sum,所求ans=max(ans,sum)\),\(s[i]为前缀异或和\)。根据字典树的特性,我们枚举r端点时,不断的插入\(s[i]\)到字典树里,再根据贪心的策略从字典树树顶往下走,假设当前位为1,那就查看这一层是否有0存在,否则反之。这样贪心后所得异或和值一定是最大的。
注意点:
1:因为求的是最短区间,假设存在两个相同的数字\(a[i]=a[j],i<j\),那么肯定优先选取\(j\)(因为我们需要最短的区间使得最大)。
2:要特判1;
具体看代码解释:
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const ll N=5*1e6;
int trie[N][2];
ll sum[N],cnt=1,ans=0,l=1,r=1,point;
void insert(int num,int idex)
{
int p=0;
for(int i=21;i>=0;i--)
{
int x=num>>i&1;
if(!trie[p][x])trie[p][x]=++point;
p=trie[p][x];
}
sum[p]=idex;
return;
}
pair<ll,int>find(int num)
{
int p=0;
ll sums=0;
for(int i=21;i>=0;i--)
{
int x=num>>i&1;
if(trie[p][!x])
{
p=trie[p][!x];
sums=sums*2+!x;
}else
{
p=trie[p][x];
sums=sums*2+x;
}
}
return {sums,sum[p]};//返回异或和以及最近坐标
}
//
void solve()
{
int n;cin>>n;
int a[n+1]={0};
for(int i=1;i<=n;i++)cin>>a[i];
insert(0,0);//先插入0,特判n=1的情况
for(int i=1;i<=n;i++)
{
a[i]^=a[i-1];//前缀和 先插入s[r];
auto t=find(a[i]);
ll sums=t.first^a[i];
//把s[r]丢进字典树里匹配
//又因为s[r]=s[1]^s[2]^s[3]....s[r]
//s[r]^(字典树现有的异或值=)s[1]^s[2]^s[3]....s[r-1];
//就相当于在字典树里找到一个i,i<r,求s[r]^s[i-1]的值最大
//因为字典树是自顶向下的,经过sum[p]=idex的预处理先,我们能够知道指针point走到哪一个i
//使得异或值最大
if(sums>ans)
{
ans=sums;
l=t.second+1;
r=i;
}
insert(a[i],i);
}
cout<<ans<<" "<<l<<" "<<r<<endl;
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t=1;
while(t--)solve();
return 0;
}

浙公网安备 33010602011771号