当我真正搞懂主席树!!!

题目链接

Problem I: qwb VS 去污棒

Time Limit: 2 Sec   Memory Limit: 256 MB
Submit: 64   Solved: 20
[ Submit][ Status][ Web Board]

Description

qwb表白学姐失败后,郁郁寡欢,整天坐在太阳底下赏月。在外人看来,他每天自言自语,其实他在和自己的影子“去污棒”聊天。
去污棒和qwb互相出题考验对方,去污棒问了qwb这样一个问题:
现已知一个有n个正整数的序列a[1],a[2]...a[n],接下来有m个操作

操作一共有两种

1.在序列末尾添加一个数x。

2.查询suf[p] xor x的最大值,其中xor是异或 ,l<=p<=r,
suf[t]表示从t开始的后缀的异或和,即 suf[t]=a[t] xor a[t+1] xor ...xor a[len],len为序列长度。

Input

第一行一个整数T(<=5),表示一共有T组数据。

每组数据第一行两个整数n(<=200000),m(<=200000),意义如上所述。

随后一行有n个数,表示初始序列。
随后m行,每行表示一个操作。
操作有两种,1: x 表示在末尾添加一个x,2: l r x表示查询suf[p] xor x的最大值,其中l<= p <= r,
所有数及x不超过224 且保证 所有操作合法。

Output

每组测试数据的第一行输出"Case x:",x为数据组数的标号,从1开始。

接下来,对每个操作2输出一行答案。

Sample Input

1
5 5
1 2 3 4 5
2 1 3 4
1 10
1 7
2 4 4 5
2 1 5 19

Sample Output

Case 1:
6
9
31

这道题,区间异或值最大!一眼看上去似乎没什么思路。但是在想一想,无论多少个值异或,二进制的位数也不会多于最大的数的位数。所以就有一种求区间最大值的感觉

!果断想到可持久化线段树--主席树!!这个题用主席树还得想一下,不能直接枚举区间,然后二分。因为异或值去看二进制比较舒服,题目又保证不大于2^24.所以直接从每个异或值得二进制的第24位开始看,是0还是1.。0就更新左树,1就更新右树!查询当然就得反过来!,为0就看右树是否更新过,如果更新了,就继续查右,否则就查左!

异或值为1同理。左树跟新过就查左,否则查右!至此,讨论完毕!!!注意坑点, 要把0点一开始存进去。。然后每次查l,r区间,记得往左推一位!!!

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 600010
#define N (1<<24)
using namespace std;
int lch[N],rch[N],size[N],root[maxn];
int n,m,tot,T,sum,op;
int modify(int pre,int i,int x)
{
    int now=++tot;
    if (i==0){lch[now]=rch[now]=0;size[now]=size[pre]+1;}
    else
    {
        int mid=((x>>(i-1))&1);
        if (mid==0)
        {
            rch[now]=rch[pre];lch[now]=modify(lch[pre],i-1,x);
        }
        else
        {
            lch[now]=lch[pre];rch[now]=modify(rch[pre],i-1,x);
        }
        size[now]=size[lch[now]]+size[rch[now]];
    }
    return now;
}
int query(int root1,int root2,int i,int x)
{
    if (i==0) return 0;
    int mid=((x>>(i-1))&1);
    if (mid==0)
    {
        if (size[rch[root2]]-size[rch[root1]]>0) return (1<<(i-1))+query(rch[root1],rch[root2],i-1,x);
        else return query(lch[root1],lch[root2],i-1,x);
    }
    else
    {
        if (size[lch[root2]]-size[lch[root1]]>0) return (1<<(i-1))+query(lch[root1],lch[root2],i-1,x);
        else return query(rch[root1],rch[root2],i-1,x);
    }
}
int main()
{
    int pp;scanf("%d",&pp);
    for (int p=1;p<=pp;p++)
    {
        printf("Case %d:\n",p);
        scanf("%d%d",&n,&T);tot=0,sum=0;
        root[0]=lch[0]=rch[0]=size[0]=0;
        root[0]=modify(root[0],25,0);
        for (int i=1;i<=n;i++)
        {
            int x;scanf("%d",&x);sum^=x;
            root[i]=modify(root[i-1],25,sum);
        }
        while (T--)
        {
            int l,r,x;scanf("%d",&op);
            if (op==1)
            {
                scanf("%d",&x);sum^=x;n++;
                root[n]=modify(root[n-1],25,sum);
            }
            else
            {
                scanf("%d%d%d",&l,&r,&x);l--;r--;
                printf("%d\n",query(root[l-1],root[r],25,sum^x));
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值