莫队算法 优雅的暴力 (Codeforences # 340 E div2)

【题目链接】点击打开链接

【解题前学习到的东西---莫队算法】

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

           例如对于多个询问[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)。

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

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

【解题思路】

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

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

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

所以对于这个问题,我们已经知道了前缀和,所以可以知道:如果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]。

【AC代码】

Cf #340 div2 E
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1<<20;

struct node{
   int l,r,id;
}Q[maxn];
int n,m,k;
int pos[maxn];
long long ans[maxn];
long long flag[maxn];
int a[maxn];
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 L=1,R=0;
long long Ans=0;
void add(int x)
{
    Ans+=flag[a[x]^k];//这里的a[x]已经表示了前缀和
    flag[a[x]]++;
}
void del(int x)
{
    flag[a[x]]--;
    Ans-=flag[a[x]^k];
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    int sz=sqrt(n);
    for(int i=1; i<=n; i++){
        scanf("%d",&a[i]);
        a[i]^=a[i-1];
        pos[i]=i/sz;
    }
    for(int i=1; i<=m; i++){
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].id=i;
    }
    sort(Q+1,Q+m+1,cmp);
    flag[0]=1;//代表每个前缀出现的次数
    for(int i=1; i<=m; i++){
        while(L<Q[i].l)
        {
            del(L-1);
            L++;
        }
        while(L>Q[i].l)
        {
            L--;
            add(L-1);
        }
        while(R<Q[i].r)
        {
            R++;
            add(R);
        }
        while(R>Q[i].r)
        {
            del(R);
            R--;
        }
        ans[Q[i].id]=Ans;
    }
    for(int i=1; i<=m; i++)
    {
        cout<<ans[i]<<endl;
    }
}

【补充】这是自己弄懂的第一个莫队的题吧,真的有被这种方法惊艳到。orzzzzzzzzzzzz I love Lucky.



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值