bzoj3261 最大异或和(可持久化Trie)

83 篇文章 0 订阅

bzoj3261 最大异或和

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3261

题意:
给定一个非负整数序列 {a},初始长度为 N。
有 M个操作,有以下两种操作类型:

1 、A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+1。
2 、Q l r x:询问操作,你需要找到一个位置 p,满足 l<=p<=r,使得:

a[p] xor a[p+1] xor … xor a[N] xor x 最大,输出最大是多少。

数据范围
N,M<=300000 ,0<=a[i]<=10^7

题解:
(重写数组版…)
因为有Add操作,维护后缀的Trie是困难的,于是考虑转化成前缀。
那么:
令pre[i]=a[1]^a[2]^……a[i]
a[p] ^ a[p+1] ^ … ^ a[N] ^ x=pre[p-1]^pre[n]^x
于是变成可持久化Trie维护各个位置的前缀异或和,在其中查询x^pre[n]与这些前缀中的一个能够异或出的最大值。

注意:
原来查询 [L,R]区间,转化后是 [L-1,R-1]的前缀,然后可持久化Trie又要用到root[(L-1)-1],
如果从1开始这就有-1了,于是从2开始,先在root[1]加个0进去。


YYR学长的平方分割大法:

我们先来考虑,若没有添加操作显然可以将所的后缀异或和建成持久化Trie树来做。
那如果添加了 k个元素呢?
查询时可以将 x异或上添加的 k个元素,再到Trie中查询。

实际上也就是每添加 lim 个元素后,重新构建数据结构。
假设查询和添加操作次数相近,那么这道题样做的话
建树总复杂度 O(N * 位数 * (N/ lim )) ,
查询总复杂度 O(N * (lim + 位数 )) 。
利用前面的方法,可以得到 lim 取 sqrt (N * (N * 位数 ) 时较优。
复杂度O(N * sqrt sqrt (N * (N * 位数 ))


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int P=30;
const int N=600005;
struct node
{
    int ch[2],size;
}tr[N*35];
int n,m,tail=0,a[N],root[N];
void insert(int pre,int &now,int val,int p)
{
    now=++tail; tr[now]=tr[pre]; tr[now].size++;
    if(p==-1) return;
    int opt=(val>>p)&1;
    insert(tr[pre].ch[opt],tr[now].ch[opt],val,p-1);
}
int query(int pre,int nd,int val,int p,int ans)
{
    if(p==-1) return ans;
    int opt=(val>>p)&1; opt=opt^1;
    int ns=tr[tr[nd].ch[opt]].size; int ps=tr[tr[pre].ch[opt]].size;
    if(ns-ps>0) return query(tr[pre].ch[opt],tr[nd].ch[opt],val,p-1,ans+(1<<p));
    else return query(tr[pre].ch[opt^1],tr[nd].ch[opt^1],val,p-1,ans);
}

int main()
{
    scanf("%d%d",&n,&m); n++;
    for(int i=2;i<=n;i++) scanf("%d",&a[i]); a[0]=a[1]=0;
    for(int i=2;i<=n;i++) a[i]=a[i-1]^a[i];
    root[0]=0; tr[0].ch[0]=tr[0].ch[1]=tr[0].size=0;
    for(int i=1;i<=n;i++) insert(root[i-1],root[i],a[i],P);
    while(m--)
    {
        char opt[5]; int x,l,r; scanf("%s",opt);
        if(opt[0]=='A')
        {
            n++; scanf("%d",&a[n]); a[n]^=a[n-1];
            insert(root[n-1],root[n],a[n],P);
        }
        else
        {
            scanf("%d%d%d",&l,&r,&x);
            x=x^a[n];
            printf("%d\n",query(root[l-1],root[r],x,P,0));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值