[解题记录] CF617E XOR and Favorite Number
CF617E XOR and Favorite Number
题意简述
- 给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),再给出 \(m\) 组询问,每组询问给出一个区间,求这个区间里面有多少个子区间的异或值为 \(k\)。
- \(1 \le n,m \le 10 ^ 5,0 \le k,a_i \le 10^6,1 \le l_i \le r_i \le n\)。
解题思路
莫队好题
首先可以想到一个暴力的 $ \mathcal O(n^4)$ 的算法,就是对于每个询问,我们都去暴力统计答案
如果知道一个性质 \(x\oplus y\oplus y =x\),那么其实我们就可以不用遍历枚举的区间,运用前缀和,转化为枚举两个端点,这样就变成了一个 \(\mathcal O(n^3)\) 的算法,但还是远远不能通过这道题
可以发现
\[a_l\oplus a_{l+1}\oplus...\oplus r =k
\]
等价于
\[s_{l-1} \oplus s_r=k
\]
这可以用莫队去维护,对于增操作,由于上面的式子又可以转化成 \(s_{l-1} =k\oplus s_r\),所以在加入点的时候我们只需要判断加入之前有多少个点的权值为 \(k\oplus s_r\),这可以用 \(cnt\) 数组维护
我们可以发现,莫队让我们从原来的枚举两个端点转化为了枚举一个端点,由此我们可以总结出一句话:
莫队本质是移动端点并维护答案,因此可以将两端点移动问题转化为一端点移动
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int N=2e6+10,INF=1e9+10;
int n,m,cnt[N],pos[N],t,k,tot,Ans[N],s[N],a[N],ans;
struct node{
int l,r,id;
}q[N];
bool cmp(node a,node b){
return pos[a.l]==pos[b.l]?a.r<b.r:a.l<b.l;
}
void add(int x){
ans+=cnt[k^x];
++cnt[x];
}
void del(int x){
--cnt[x];
ans-=cnt[k^x];
}
signed main(){
//freopen("CF617E.in","r",stdin);
//freopen("CF617E.out","w",stdout);
n=read();m=read();k=read();
t=sqrt(m);
for(int i=1;i<=n;++i) a[i]=read(),pos[i]=(i-1)/t+1;
for(int i=1;i<=n;++i) s[i]=s[i-1]^a[i];
for(int i=1;i<=m;++i){
q[i].l=read(),q[i].r=read();q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;++i){
int ql=q[i].l-1,qr=q[i].r;
while(l<ql) del(s[l++]);
while(l>ql) add(s[--l]);
while(r<qr) add(s[++r]);
while(r>qr) del(s[r--]);
Ans[q[i].id]=ans;
}
for(int i=1;i<=m;++i) printf("%lld\n",Ans[i]);
return 0;
}

浙公网安备 33010602011771号