牛异或(区间异或)

题目地址: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;
}
posted @ 2023-06-07 20:05  五柳呢  阅读(61)  评论(0)    收藏  举报