洛谷 4462 & bzoj5301 [CQOI2018]异或序列 题解(莫队)

博客介绍了洛谷4462与bzoj5301题目,涉及异或序列问题。通过转化异或和公式,使用莫队算法处理询问范围内的异或对数。文章提供了思路解析和代码实现。

原题链接:
洛谷
bzoj

题意简述

给定一个序列(长度 n &lt; = 1 e 5 n&lt;=1e5 n<=1e5,每个数 a i &lt; = 1 e 5 a_i&lt;=1e5 ai<=1e5),和一个常数 k &lt; = 1 e 5 k&lt;=1e5 k<=1e5。还有一些( &lt; = 1 0 5 &lt;=10^5 <=105)个询问 l , r ( 1 &lt; = l , r &lt; = n , k &lt; = 1 0 5 ) l,r(1&lt;=l,r&lt;=n,k&lt;=10^5) l,r(1<=l,r<=n,k<=105),问:有多少 ( x , y ) (x,y) (x,y)满足:

  1. x x x y y y的异或和为 k k k
  2. l &lt; = x &lt; = y &lt; = r l&lt;=x&lt;=y&lt;=r l<=x<=y<=r

(详细数据呢,以后就不写了,懒得写,也没什么用,我觉得关键还是在思路)

思路

(注:以下 ⊕ \oplus 运算表示异或)
首先我们要转化一下异或和的式子。我们处理一个前缀异或值 s [ ] s[] s[] s [ i ] s[i] s[i]表示 [ 1 , i ] [1,i] [1,i]的异或和。由于异或的逆运算是异或,那么 [ x , y ] [x,y] [x,y]的异或和就是 s [ y ] ⊕ s [ x − 1 ] s[y]\oplus s[x-1] s[y]s[x1]

那现在询问就变成了:给定一些 l , r l,r l,r,问 [ l − 1 , r ] [l-1,r] [l1,r]中有多少对数异或值为 k k k
考虑到每个数 &lt; = 1 0 5 &lt;=10^5 <=105,那么(目测)异或值最大不会超过 3 e 5 3e5 3e5。反正不会 M L E MLE MLE,开他娘的即珂。然后我们就用莫队分块做这个东西了。设
c n t [ x ] cnt[x] cnt[x]表示:当前考虑的区间内有多少个数是 x x x
然后有一个全局变量 a n s ans ans表示当前的答案。
对于新加入的点 y y y
a n s + = c n t [ k ⊕ y ] ans+=cnt[k\oplus y] ans+=cnt[ky](因为 y ⊕ ( k ⊕ y ) = k y \oplus (k\oplus y)=k y(ky)=k,所以对于新加入的点 y y y,只要能找到 k ⊕ y k\oplus y ky就能和其异或凑出 k k k
c n t [ y ] + + cnt[y]++ cnt[y]++(这个不用解释吧)
删除类似。差不多写即珂。(都到这了代码还不会写,那么也许是因为你对莫队的理解还不够深?如果是的话,珂以考虑参考我的蒟蒻代码)
代码:

// luogu-judger-enable-o2
//开挂的痕迹
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 300100
    int n,m,k;
    int a[N];
    int sn;//sn=sqrt(n)

    struct node//offline queries
    {
        int l,r;
        int id,ans;
    }Q[N];
    bool operator<(node a,node b)
    {
        int ali=(a.l-1)/sn+1,bli=(b.l-1)/sn+1;
        if (ali!=bli) return ali<bli;
        else return a.r<b.r;
    }
    bool cmp_id(node a,node b)
    {
        return a.id<b.id;
    }

    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Input()
    {
        R1(n),R1(m),R1(k);
        for(int i=1;i<=n;++i)
        {
            R1(a[i]);a[i]^=a[i-1];
        }
        sn=sqrt(n+0.05);
        for(int i=1;i<=m;++i)
        {
            int l,r;
            R1(l),R1(r);--l;
            Q[i]=(node){l,r,i,-1};
        }
        sort(Q+1,Q+m+1);
    }

    int cnt[N],nowans;
    void Add(int pos)//加入新点pos
    {
        int val=a[pos];
        nowans+=cnt[val^k];
        ++cnt[val];
    }
    void Del(int pos)//删除点pos
    {
        int val=a[pos];
        --cnt[val];
        nowans-=cnt[val^k];
        //这个不是很显然么。。。
    }

    void Soviet()
    {
        int l=1,r=0;
        for(int i=1;i<=m;++i)
        {
            while(l<Q[i].l) Del(l),++l;
            while(l>Q[i].l) --l,Add(l);
            while(r<Q[i].r) ++r,Add(r);
            while(r>Q[i].r) Del(r),--r;
            //不断滑动区间

            Q[i].ans=nowans;//记录答案
        }
        sort(Q+1,Q+m+1,cmp_id);
        for(int i=1;i<=m;++i)
        {
            printf("%d\n",Q[i].ans);
        }
    }

    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值