bzoj5301 异或序列

Description

已知一个长度为 nn 的整数数列 a[1],a[2],…,a[n]a[1],a[2],…,a[n] ,给定查询参数 ll 、 rr ,问在 [l,r][l,r] 区间内,有多少连续子序列满足异或和等于 kk 。 
也就是说,对于所有的 xx , yy (l≤x≤y≤r)(l≤x≤y≤r) ,能够满足 
a[x] xor a[x+1] xor … xor a[y]=ka[x] xor a[x+1] xor … xor a[y]=k 的 xx ,yy 有多少组。

Input

输入文件第一行,为 33 个整数 nn , mm , kk 。 
第二行为空格分开的 nn 个整数,即 a1,a2,…,ana1,a2,…,an 。 
接下来 mm 行,每行两个整数lj,rjlj,rj ,表示一次查询。 
1≤n,m≤105,0≤k,ai≤105,1≤lj≤rj≤n1≤n,m≤105,0≤k,ai≤105,1≤lj≤rj≤n

Output

输出文件共 mm 行,对应每个查询的计算结果。

Sample Input

4 5 1 
1 2 3 1 
1 4 
1 3 
2 3 
2 4 
4 4

Sample Output





1


据说是裸莫队,可惜我比较菜,理解不能呢

代码转载自这儿、

cnt【i】存的是,区间内i出现的次数,sum[i]=a[1]^a[2]^a[3]……a[i],

那么那4个while解释第一个一个,剩余都一样的理解,

设某个数x=k^sum[l],显然x^sum[l]=k,那么cnt[k^sum[l]]=cnt[x],也就是区间内x出现的个数,

那么l要++,cnt[sum[l]]就要--,因为区间内sum[l]消失了,自然先前所有x^sum[l]=k的x都要减掉

所以是ans-=cnt[k^sum[l]],这样相当于区间l,r状态转移到l+1,r,继续直到l等于要查询区间的l,r等于要查询区间的r。

然后answer【.id】,因为要输出的顺序是原来的,现在的操作是建立在被sort后(这也是莫队的精华)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 2000010
struct data
{
    int l,r,id,block;
}q[N];
int n,m,k;
ll ans;
ll sum[N],cnt[N],a[N];
ll answer[N];
bool cp(data x,data y)
{
    if(x.block!=y.block) return x.block<y.block;
    return x.r<y.r;
}
void solve()
{
    sort(q+1,q+m+1,cp);
    int l=1,r=0;
    for(int i=1;i<=m;i++)
    {
        while(l<q[i].l-1) {
            cnt[sum[l]]--; ans-=cnt[k^sum[l]]; l++;
        }
        while(l>q[i].l-1) {
            l--; ans+=cnt[k^sum[l]]; cnt[sum[l]]++;
        }
        while(r<q[i].r) {
            r++; ans+=cnt[k^sum[r]]; cnt[sum[r]]++;
        }
        while(r>q[i].r) {
            cnt[sum[r]]--; ans-=cnt[k^sum[r]]; r--;
        }
        answer[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++) printf("%lld\n",answer[i]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int size=(int)(sqrt(n));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i; q[i].block=(q[i].l-1)/size;
    }
    sum[1]=a[1];
    for(int i=2;i<=n;i++) sum[i]=a[i]^sum[i-1];
    solve();
    return 0;
}

自己敲一遍,加深印象

#include <bits/stdc++.h>
#define ll long long
#define N (ll)(2e6+10)
using namespace std;
struct data
{
    int l,r,id,block;
    bool operator < (const data &a) const{
        if(block!=a.block)
            return block < a.block;
        return r < a.r;
    }
}Data[N];
int n,m,k;
ll ans;
ll cnt[N],a[N];
ll answer[N];
void Solve()
{
    sort(Data+1,Data+1+m);
    int l,r;
    l = 1, r = 0;
    for(int i=1;i<=m;i++)
    {
        while(l<Data[i].l-1)
            cnt[a[l]]--,ans-=cnt[k^a[l]],l++;
        while(l>Data[i].l-1)
            l--,ans+=cnt[k^a[l]],cnt[a[l]]++;
        while(r<Data[i].r)
            r++,ans+=cnt[k^a[r]],cnt[a[r]]++;
        while(r>Data[i].r)
            cnt[a[r]]--,ans-=cnt[k^a[r]],r--;
        answer[Data[i].id]=ans;
    }
    for(int i=1;i<=m;i++)
        printf("%lld\n",answer[i]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),i>1?a[i]^=a[i-1]:1;
    int Size=(int)(sqrt(n));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&Data[i].l,&Data[i].r);
        Data[i].id=i; Data[i].block=(Data[i].l-1)/Size;
    }
    Solve();
    return 0;
}

莫队讲解转载自、

看一个例题:给定一个n(n<50000)元素序列,有m(m<200000)个离线查询。每次查询一个区间L~R,问每个元素出现次数为k的有几个。(必须恰好是k,不能大于也不能小于)

我们很容易想到用线段树或者树状数组直接做,但是我们想,如果是用线段树或者树状数组做而且我们不会优化的话(请dalao无视掉,您可以直接线段树做了。)每次修改和维护会很麻烦,线

段树和树状数组的优势体现不出来。

这时候就要使用莫队算法了。

三.莫队算法的思想怎么理解?

接着上面的例题,直接暴力怎么样??

肯定会T的啊。(luogu P1972 [SDOI2009]HH的项链 原数据居然可以mn模拟过......当然现在不行了)

但是如果这个暴力我们给优化一下呢?

我们想,有两个指针curL和curR,curL指向L,curR指向R。

L和R是一个区间的左右两端点。

我们先计算一个区间[curL curR]的answer,这样的话,我们就可以用O(1)转移到[curL-1 curR] [curL+1 curR] [curL curR+1] [curL curR-1]上来并且求出这些区间的answer。

我们利用curL和curR,就可以移动到我们所需要求的[L R]上啦~

这样做会快很多,但是......

如果有个**数据,让你在每个L和R间来回跑,而且跨度很大呢??

我们每次只动一步,岂不是又T了??

但是这其实就是莫队算法的核心了。我们的莫队算法还有优化。

这就是莫队算法最精明的地方(我认为的qwq),也正是有了这个优化,莫队算法被称为:优雅的暴力

我们想,因为每次查询是离线的,所以我们先给每次的查询排一个序。

我们把所有的元素分成多个块(即分块)。分了块跑的会更快。再按照左端点从小到大,左端点相同按右端点从小到大。

这样对于不同的查询

例如:

我们有长度为9的序列。

1 2 3 4 5 6 7 8 9 分为1——3 4——6 7——9

查询有7组。[1 2] [2 4] [1 3] [6 9] [5 8] [3 8] [8 9]

排序后就是:[1 2] [1 3] [2 4] [3 8] | [5 8] [6 9] | [8 9]

然后我们按照这个顺序移动指针就好啦~

这样,不断地移动端点指针+精妙的排序,就是普通莫队的思想啦~

四.具体代码实现:

1.对于每组查询的记录和排序:

l,r为左右区间编号,p是第几组查询的编号

struct query{
    int l, r, p;
}e[maxn];

bool cmp(query a, query b)
{
    return (a.l/bl) == (b.l/bl) ? a.r < b.r : a.l < b.l;
}

2.处理和初始变量:

answer就是所求答案,bl是分块数量,a[]是原序列,ans[]是记录原查询序列下的答案,cnt[]是记录对于每个数i,cnt[i]表示i出现过的次数,curL和curR不再解释,nmk题意要求。

int answer, a[maxn], m, n, bl, ans[maxn], cnt[maxn], k, curL = 1, curR = 0;
void add(int pos)//添加 
{
    //do sth...
}
void remove(int pos)//去除 
{
    //do sth...
}
//一般写法都是边处理 边根据处理求答案。cnt[a[pos]]就是在pos位置上原序列a出现的次数。 

3.主体部分及输出:

预处理查询编号,用四个while移动指针顺便处理。

在这里着重说下四个while

我们设想有一条数轴:

技术分享图片

当curL < L 时,我们当前curL是已经处理好的了。所以remove时先去除当前curL再++

当curL > L 时,我们当前curL是已经处理好的了。所以 add  时先--再加上改后curL

当curR > R 时,我们当前curR是已经处理好的了。所以remove时先去除当前curR再--

当curR < R 时,我们当前curR是已经处理好的了。所以 add  时先++再加上改后curR

猜你喜欢

转载自blog.csdn.net/Du_Mingm/article/details/81502923