Codeforces #340 div2 E. XOR and Favorite Number(莫队算法)

题目链接:

http://codeforces.com/contest/617/problem/E

题目大意:

有n 个数,m个询问。每次询问在区间[l,r]里面,有多少种情况使得ai^ai+1^……^aj=k。

范围:1 ≤ n, m ≤ 100 0000 ≤ k ≤ 1 000 000。

思路:

对于这类区间上的问题,我们可以获得数组的异或前缀和pre[i]=a1^a2......ai。

这样对于区间[l,r],我们可以知道这个区间的异或值为pre[l-1]^pre[r]。

对于不需要修改的区间询问问题,可以采用莫队算法。

莫队算法:在我现在粗浅的认知看来,莫队算法的主要精髓就是面对已知的询问信息,将这些询问进行顺序调整然后离线操作。

例如对于多个询问[l1,r1],[l2,r2]……[li,ri],如果已知了[l1,r1]的结果,那么我们肯定很快能够求出[l1+1,r1+1],[l1-1,r1+1],[l1-1,r1-1],[l1+1,r1-1]的结果。所以当我们去看第二个询问的时候,我们就可以通过第一个询问的结论,一步步地从[l1,r1]走向[l2,r2],此时我们所需要的时间就是O(|L2-L1|+|R2-R1|),这个有一个名字叫做曼哈顿距离。于是问题就变成了构造曼哈顿最小生成树,然后按照生成树的顺序进行求解,使得速度大大提升。

但是求曼哈顿生成树比较困难,然而还有一种分块思想,能够比较迅速而高效地解决这个问题。

我们把n个数分块成为sqrt(n)块,然后把询问进行排序:如果两个询问的l是在同一块里面的,就按r从小到大排。如果不同块,就按照块从小到大排。在同一个块里面转移的时候,由于每个块里面有n^0.5个元素,所以转移最大时间复杂度就是O((n^0.5)*(n^0.5))=O(n)。如果在不同块,最大复杂度是O(n^1.5)。所以最终时间复杂度为O(n^1.5)。

------------------------------------------------------------------------------

所以对于这个问题,我们已经知道了前缀和,所以可以知道:如果pre[i-1]^pre[j]=k,那么就有pre[j]^k=pre[i-1]。这个时候我们开一个数组num,来记录pre[i-1]^pre[j]=k时候的个数,我们转移时就只需要对答案sum+=num[pre[i-1]^pre[j]]就可以了,再化简一下就是,sum+=num[pre[j]^k]。

代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<stdlib.h>
#define ll __int64
#define M 100010
using namespace std;
ll n,m,k,a[M],pre[M],ans[M],block,pos[M],num[M*50],sum;
struct node {
 ll l,r,id;
}Q[M];
bool cmp(node a,node b )
{
    if(pos[a.l]==pos[b.l])return a.r<b.r;
    return pos[a.l]<pos[b.l];
}
int main()
{
    ll i,j;
    while(~(scanf("%I64d%I64d%I64d",&n,&m,&k)))
    {
        sum=0;
        memset(num,0,sizeof(num));
        memset(pre,0,sizeof(pre));
        block=ceil(sqrt(1.0*n)); //分块
        for(i=1;i<=n;i++)
        {
            scanf("%I64d",&a[i]);
            if(i==1)pre[i]=a[i];
            else pre[i]=pre[i-1]^a[i];    //求前缀数组
            pos[i]=(i-1)/block;
        }
        for(i=1;i<=m;i++)
        {
            scanf("%I64d%I64d",&Q[i].l,&Q[i].r);
            Q[i].id=i;
         //   pos[i]=i/block;
        }
        sort(Q+1,Q+m+1,cmp);
        ll L,R;
        L=1;R=0;
        num[0]++;
        for(i=1;i<=m;i++)
        {
            ll id=Q[i].id;
            while(R<Q[i].r){
                R++;
                sum+=num[pre[R]^k];
                num[pre[R]]++;   //记录出现次数
            }

            while(R>Q[i].r)
            {
                num[pre[R]]--;
                sum-=num[pre[R]^k];
                R--;
            }

           while(L<Q[i].l)
            {
                num[pre[L-1]]--;
                sum-=num[pre[L-1]^k];
                L++;
            }

            while(L>Q[i].l)
            {
                L--;
                sum+=num[pre[L-1]^k];
                num[pre[L-1]]++;

            }

           // printf("%I64d %I64d\n",L,R);
            ans[id]=sum;
        }
        for(i=1;i<=m;i++)
            printf("%I64d\n",ans[i]);
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值