【题目链接】点击打开链接
【解题前学习到的东西---莫队算法】
在我现在粗浅的认知看来,莫队算法的主要精髓就是面对已知的询问信息,将这些询问进行顺序调整然后离线操作。
例如对于多个询问[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.