【USACO6.1.3】Cow XOR奶牛异或 trie

NKOJ 1873 奶牛异或

问题描述

农民约翰在喂奶牛的时候被另一个问题卡住了。他的所有N(1 <= N <= 100,000)个奶牛在他面前排成一行(按序号1..N的顺序),按照它们的社会等级排序。奶牛#1有最高的社会等级,奶牛#N最低。每个奶牛同时被指定了一个不唯一的附加值,这个数在0..2^21 - 1的范围内。
帮助农民约翰找出应该从哪一头奶牛开始喂,使得从这头奶牛开始的一个连续的子序列上,奶牛的附加值的异或最大。
如果有多个这样的子序列,选择结尾的奶牛社会等级最高的。如果还不唯一,选择最短的。

输入格式

第1行:一个单独的整数N。
第2到N + 1行:N个0..2^21 - 1之间的整数,代表每头奶牛的被赋予的数。第j行描述了社会等级j - 1的奶牛。

输出格式

第 1 行: 3个空格隔开的整数,分别为:最大的异或值,序列的起始位置、终止位置。

样例输入

5
1
0
5
4
2

样例输出

6 4 5


对于异或,由于这是一个按位的运算方式,也就是说,这一位的结果不会对其他的位造成任何影响。基于这个性质,我们可以贪心处理下面的问题:

如果给你一个数 a 和其他一些数,如何求a与其他数中的一个异或起来的最大值?

根据贪心原则,把所有数写成二进制的形式后,从高位开始讨论。那么 a 异或起来更大的显然是较高位上与a不相同的。

现在考虑这个问题。连续一段数的异或和,容易想到用前缀异或和处理。对于一个固定的右端点,如何找到左边与它异或起来最大的前缀异或和?暴力会使整个算法是 O(N2) 的,显然不可取。

正确的做法是把所有前缀异或和在写成二进制后插入到一个trie里。根据trie的性质,每一条从根节点出发的由上到下的路径都是一个前缀异或和。根据前面的贪心原则,设当前讨论数的某一位为x,那么如果当前trie的节点有x^1这个儿子,那么选择这个儿子走下去肯定是更优的,否则就只好走x这个儿子。按照这个方法走下去之后就在 O(len) 的复杂度里找到最优值。


很早之前的题了,代码有点丑,可以参照另一道类似的题的代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
struct node{int son[2],num;}trie[2000005];
int n,i,tot=1,sum[100005],ans,a=1,b=1;
void ins(int c,int k)
{
    int i,t,p=1;
    for(i=20;i>=0;i--)
    {
        t=(c>>i)&1;
        if(trie[p].son[t]==0)
        {
            trie[p].son[t]=++tot;
            p=tot;
            trie[p].num=0;
        }
        else p=trie[p].son[t];
    }
    trie[p].num=k;
}
int f(int c)
{
    int i,t,p=1;
    for(i=20;i>=0;i--)
    {
        t=(c>>i)&1;
        if(trie[p].son[1^t]==0)p=trie[p].son[t];
        else p=trie[p].son[1^t];
    }
    return trie[p].num;
}
main()
{
    int x,i,tt;
    scanf("%d",&n);
    ins(0,0);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&x);
        sum[i]=sum[i-1]^x;
        ins(sum[i],i);
        tt=f(sum[i]);
        if((sum[i]^sum[tt])>ans)
        {
            a=tt+1;b=i;ans=sum[i]^sum[tt];
        }
    }
    cout<<ans<<" "<<a<<" "<<b;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值