想了好久才明白zz
来源?:莫队题单
题目大意
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),再给出 \(m\) 组询问,每组询问给出一个区间,求这个区间里面有多少个子区间的异或值为 \(k\)。
- \(1 \le n,m \le 10^5\)
- \(0 \le k,a_i \le 10^6\)
- \(1 \le l_i,r_i \le n\)
题目分析
读完题后我是一脸懵的qwq
不会异或!!
不会查询!!
好,那就不要异或也不要查询!!(・o・)
简化 1
给定一个长度为 \(n\) 的序列 \(a\),求这个序列的和。
这也太简了
直接算 \(\sum\limits^{n}_{i=1} a_i\) 即可!
简化 2
给定一个长度为 \(n\) 的序列 \(a\),给出 \(m\) 组询问,每组询问给出一个区间,求这个区间的和。
蓝题变橙题
首先当然可以直接模拟,每次询问计算 \(\sum\limits_{i=l}^{r} a_i\)。可是时间复杂度 \(O(nm)\),不理想。
那么优化就是...
前缀和!
首先设 \(a_0=0\),因为无修改所以可以开一个数组 \(\text{pf}\) 记录 \(a\) 的前缀和。设
\[\text{pf}_x=\sum^{x}_{i=0} a_i \]我们有
\[\begin{align*} \sum^{r}_{i=l}a_i&=\sum^{r}_{i=0}a_i-\sum^{l-1}_{i=0}a_i\\ &= \text{pf}_r-\text{pf}_{l-1} \end{align*} \]时间复杂度:\(O(n)\) 预处理,\(O(1)\) 查询
简化 3
给定一个长度为 \(n\) 的序列 \(a\),给出 \(m\) 组询问,每组询问给出一个区间,求这个区间的异或值。
首先定义 \(\oplus\) 为异或符号,\(\bigoplus\limits_{i=l}^{r}a_i=a_l \oplus a_{l+1} \oplus \dots \oplus a_{r-1} \oplus a_r\)。
发现这题与上一题非常相似,考虑是否可以利用同样的思想。
考虑 \(a_0\) 的值。当我们在求和时,我们设 \(a_0=0\)。 为什么?
是因为我们知道对于任意 \(x\),\(x+0=x\) 成立。在这里我们称 \(0\) 为加法的单位元。
同理对于任意 \(x\),\(x \times 1=x\) 成立。所以 \(1\) 为乘法的单位元。
那么异或呢?对于任意 \(x\),\(x \oplus 0=x\) 成立。所以 \(0\) 为异或的单位元。
所以我们同样设 \(a_0=0\)。
继续开一个数组 \(\text{pf}\) 记录 \(a\) 的前缀异或。设
\[\text{pf}_x=\bigoplus\limits_{i=0}^{x} a_i \]要如何得到区间异或呢?同样先从和观察。
\[\begin{align*} \sum_{i=0}^{l-1}a_i + \sum_{i=l}^{r}a_i &= \sum_{i=0}^{r}a_i\\ \sum_{i=l}^{r}a_i &= \sum_{i=0}^{r}a_i - \sum_{i=0}^{l-1}a_i\\ &= \text{pf}_r - \text{pf}_{l-1} \end{align*} \]注意到第二步我们将 \(+\) 号变成了 \(-\) 号,为什么?
因为减法是加法的逆运算!对于任意 \(u,v,w\) 满足 \(u+v=w\),\(u=w-v\) 成立。
那么异或呢?观察发现异或的逆运算是异或!
也就是对于任意 \(u,v,w\) 满足 \(u \oplus v = w\),\(u = w \oplus v\) 成立。
因为异或满足交换律,所以 \(u = v \oplus w\) 也成立。
回到前缀异或。现在我们有
\[\begin{align*} \bigoplus_{i=0}^{l-1}a_i \oplus \bigoplus_{i=l}^{r}a_i &= \bigoplus_{i=0}^{r}a_i\\ \bigoplus_{i=l}^{r}a_i &= \bigoplus_{i=0}^{r}a_i \oplus \bigoplus_{i=0}^{l-1}a_i\\ &= \text{pf}_r \oplus \text{pf}_{l-1} \end{align*} \]时间复杂度:\(O(n)\) 预处理,\(O(1)\) 查询
简化 4
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),求这个序列里面有多少个子区间的和为 \(k\)。
这里开始就有一点难度了 主要是我菜
发现可以转化题目为
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),求有多少个 \(l,r\) 满足 \(\sum\limits_{i=l}^{r} a_i=k\)。
直接遍历可得 \(O(n^3)\) 解法
int ans=0;
for(int len=1;len<=n;len++){
for(int l=0;l<=n-len;l++){
int r=l+len-1,sum=0;
for(int i=l;i<=r;i++){
sum+=a[i];
}
if(sum==k) ans++;
}
}
时间复杂度证明
$$ \begin{align*} \sum_{L=1}^{n}\sum_{l=0}^{n-L}\sum_{i=l}^{l+L-1} 1 &= \sum_{L=1}^{n}\sum_{l=0}^{n-L} L\\ &= \sum_{L=1}^{n} (L(n-L+1))\\ &= \sum_{L=1}^{n} (nL-L^2+L)\\ &= \sum_{L=1}^{n} nL - \sum_{L=1}^{n} L^2 + \sum_{L=1}^{n} L\\ &= n \left(\sum_{L=1}^{n} L\right) + \sum_{L=1}^{n} L - \sum_{L=1}^{n} L^2\\ &= (n+1)\left(\sum_{L=1}^{n} L\right) - \sum_{L=1}^{n} L^2\\ &= (n+1)\left(\dfrac{n(n+1)}{2}\right) - \dfrac{n(n+1)(2n+1)}{6}\\ &= \dfrac{1}{2}\left(n^3+2n^2+n\right) - \dfrac{1}{6}\left(2n^3+3n^2+n\right)\\ &= \dfrac{1}{6}\left(3n^3+6n^2+3n\right) - \dfrac{1}{6}\left(2n^3+3n^2+n\right)\\ &= \dfrac{1}{6}\left(n^3+9n^2+4n\right) \end{align*} $$ 可得 $$O\left(\dfrac{1}{6}\left(n^3+9n^2+4n\right)\right) = O(n^3)$$利用前缀和可以优化成 \(O(n^2)\)
能继续优化吗?
观察我们可以利用简化 2 的思想得到
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),求有多少个 \(l,r\) 满足 \(\text{pf}_r-\text{pf}_{l-1}=k\)。
\(l \le r\) 必定成立。
遍历 \(r=[1,n]\),发现区间 \([l,r]\) 要满足 \(\text{pf}_r-\text{pf}_{l-1}=k\),必定有 \(\text{pf}_{l-1}=\text{pf}_r-k\)。
定义 \(cnt_x\) 为 \(x\) 出现的次数,初始化 \(cnt_x=0\)。因为 \(l \le r\) 成立,所以对于任意 \(r\),\(l-1\) 的取值为 \([0,r-1]\)。因此仅需在查询 \(cnt_{\text{pf}_r-k}\) 后将 \(cnt_{\text{pf}_r}\) 的值加上 \(1\) 即可。
可得答案为
\[\sum_{r=0}^{n}cnt_{\text{pf}_r-k} \]需要注意的是 \(a_i\) 的大小。当
- \(1 \le n \le 10^5\)
- \(0 \le a_i \le 10^6\)
\(\text{pf}_i\) 的最大值为 \(10^5 \times 10^6 = 10^{11}\),会 MLE。可以用 STL map
来维护,单次查询 \(O(\log n)\)。
为什么不用 unordered_map
有机会被卡成单次 $O(n)$,总 $O(n^2)$
详情见 Blowing up unordered_map, and how to stop getting hacked on it
总时间复杂度:\(O(n \log n)\)
简化 5
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),求这个序列里面有多少个子区间的异或值为 \(k\)。
合并简化 3 和简化 4 的结论,答案就是
\[\sum_{r=0}^{n}cnt_{\text{pf}_{r} \oplus k} \]如果明白可以跳过这个
利用简化 3 转化题目为给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),求有多少个 \(l,r\) 满足 \(\text{pf}_r \oplus \text{pf}_{l-1}=k\)。
\(l \le r\) 必定成立。
遍历 \(r=[1,n]\),发现区间 \([l,r]\) 要满足 \(\text{pf}_r \oplus \text{pf}_{l-1}=k\),必定有 \(\text{pf}_{l-1}=\text{pf}_r \oplus k\)。
定义 \(cnt_x\) 为 \(x\) 出现的次数,初始化 \(cnt_x=0\)。因为 \(l \le r\) 成立,所以对于任意 \(r\),\(l-1\) 的取值为 \([0,r-1]\)。因此仅需在查询 \(cnt_{\text{pf}_r \oplus k}\) 后将 \(cnt_{\text{pf}_r}\) 的值加上 \(1\) 即可。
可得答案为
\[\sum_{r=0}^{n}cnt_{\text{pf}_r \oplus k} \]题目解法
经过多轮的简化已经看得出原题了!贴回原题:
给定一个长度为 \(n\) 的序列 \(a\),然后再给一个数字 \(k\),再给出 \(m\) 组询问,每组询问给出一个区间,求这个区间里面有多少个子区间的异或值为 \(k\)。
最后要处理的是回答 \(m\) 组询问。终于要请出今天的主角:莫队算法!
不过经费不足所以腰斩了有机会再更
莫队算法可以参考这位dalao的博客:莫队算法——从入门到黑题
利用简化 5 的结论维护数组 \(cnt\)。在移动 \(l,r\) 指针时更新 \(cnt\)。对于区间 \([l,r]\),答案为
\[\sum_{i=l-1}^{r}cnt_{\text{pf}_i \oplus k} \]注意:因为 \(a_i \le 10^6\),如果用数组维护 \(cnt\),需要将容量设为 \(2^{\lceil\log_2 10^6\rceil}=2^{20}=\) 1<<20
。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,k;
ll a[100020],pf[100020];
ll sq,bnum;
ll blg[100020],cnt[1<<20];
struct Mo{
ll l,r,id;
bool operator <(const Mo &rhs) const{
return blg[l]==blg[rhs.l] ? r<rhs.r : l<rhs.l;
}
};
Mo query[100020];
ll ans=0;
void add(ll x){
ans+=cnt[pf[x]^k];
cnt[pf[x]]++;
}
void del(ll x){
cnt[pf[x]]--;
ans-=cnt[pf[x]^k];
}
int main() {
cin>>n>>m>>k;
a[0]=0;
for(int i=1;i<=n;i++){
cin>>a[i];
}
pf[0]=a[0];
for(int i=1;i<=n;i++){
pf[i]=pf[i-1]^a[i];
}
sq=sqrt(n);bnum=ceil((double)n/sq);
for(int i=0;i<=bnum;i++){
for(int j=0;j<sq;j++){
if(i*sq+j+1>n)break;
blg[i*sq+j+1]=i;
}
}
for(int i=0;i<m;i++){
ll x,y;cin>>x>>y;
query[i].l=--x;
query[i].r=y;
query[i].id=i;
}
sort(query,query+m);
memset(cnt,0,sizeof(cnt));
vector<ll> output(m);
ll L=0,R=-1;
for(int i=0;i<m;i++){
ll l=query[i].l,r=query[i].r,id=query[i].id;
while(L<l) del(L++);
while(L>l) add(--L);
while(R<r) add(++R);
while(R>r) del(R--);
output[id]=ans;
}
for(ll x:output)cout<<x<<'\n';
}
后记
是道好题,题解写了好久qwq
如果有任何疑问或错误漏缺欢迎在评论告诉我!
~~~ 完结撒花 ✿✿ヽ(°▽°)ノ✿ ~~~
标签:cnt,XOR,text,sum,Favorite,异或,pf,题解,oplus From: https://www.cnblogs.com/lumid/p/18119967