# 莫队阶段小结

首先,为什么要叫小结呢,因为我只学了一点点,后续可能更多

## 莫队

莫队是一种离线处理区间问题的神器.答题思路就是你将原数列分成$\sqrt{n}$块,将所有查询左端点定位,并按照左端点所在的块进行排序,相同则按照右端点排序

大体就是这个样子

```cpp
inline bool cmp(Q x,Q y){
return belong[x.li] == belong[y.li] ? x.ri < y.ri : x.li < y.li;
}
```

这样的话我们每个快内都暴力求

时间复杂度为$O(m\sqrt{n})$

但是还有一种玄学加速方式,十分有用

```cpp
inline bool cmp(Q x,Q y){
return belong[x.li] ^ belong[y.li] ? belong[x.li] < belong[y.li] : belong[x.li] & 1 ? x.ri < y.ri : x.ri > y.ri;
}
```

会快很多.

之后我们每次维护取件区间,和当前左右端点.每次发现不在当前区域内就暴力跳的同时修改信息

```cpp
int nowl = 1,nowr = 0;
for(int i = 1;i <= m;++i){
int L = q[i].li,R = q[i].ri;
while(nowl > L) add(--nowl);
while(nowr < R) add(++nowr);
while(nowl < L) del(nowl++);
while(nowr > R) del(nowr--);
//统计答案
}
```

另外注意应当先拓展后收缩,为了该开始改成负数之类的东西。

接下来放几道例题

### [luoguP3901 数列找不同](https://www.luogu.org/problemnew/show/P3901)

莫队的板子题了,我们维护$sum_i$表示$i$这个数当前出现了多少次,$ans$表示当前出现次数$>=2$的数的个数

每次修改时更新$sum$和$ans$,之后统计答案即可

```cpp
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int N = 1e5 + 3;
int a[N],ans[N];
int sum[N];
int n,m,now = 0,bl;
int belong[N],block;
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
struct Q{
int li,ri;
int id;
}q[N];
inline bool cmp(Q x,Q y){
return belong[x.li] == belong[y.li] ? x.ri < y.ri : x.li < y.li;
}
inline void add(int x){
x = a[x];
++sum[x];
if(sum[x] == 2) now++;
}
inline void del(int x){
x = a[x];
--sum[x];
if(sum[x] == 1) now--;
}
int main(){
n = read(),m = read();
for(int i = 1;i <= n;++i) a[i] = read();
bl = sqrt(n);
for(int i = 1;i <= n;++i) belong[i] = i / bl;
for(int i = 1;i <= m;++i) q[i].li = read(),q[i].ri = read(),q[i].id = i;
sort(q + 1,q + m + 1,cmp);
int nowl = 1,nowr = 0;
for(int i = 1;i <= m;++i){
int L = q[i].li,R = q[i].ri;
while(nowl < L) del(nowl++);
while(nowl > L) add(--nowl);
while(nowr > R) del(nowr--);
while(nowr < R) add(++nowr);
ans[q[i].id] = now;
}
for(int i = 1;i <= m;++i){
if(ans[i]) printf("No\n");
else printf("Yes\n");
}
return 0;
}
```

### [CF375D Tree and Queries](https://www.luogu.org/problemnew/show/CF375D)

虽然是树上问题,但是所有的询问都是针对子树,我们就可以针对他的欧拉序进行操作.
但是比较棘手的是所有的$k_i$可能不相同,

我们就设$sum_i$表示$>=i$的数的个数

$val_i$表示$i$的出现次数

每次修改统计即可

答案就是$sum[k_i]$

```cpp
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<vector>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
const int N = 1e5 + 3;
int n,m;
vector <int> G[N];
int v[N],sum[N],val[N];
int l[N],r[N],c[N];
int belong[N];
int ans[N],tot,cnt,bl;
struct Q{
int li,ri;
int id;
int ko;
}q[N];
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline void dfs(int x,int f){
c[++cnt] = v[x];
l[x] = cnt;
for(int i = 0;i < (int)G[x].size();++i){
int y = G[x][i];
if(y == f) continue;
dfs(y,x);
}
r[x] = cnt;
}
inline bool cmp(Q x,Q y){
return belong[x.li] == belong[y.li] ? x.ri < y.ri : x.li < y.li;
}
//sum[i]:>=i的数的种类数
//val[i]:i出现次数
inline void add(int x){
x = c[x];
val[x]++;
sum[val[x]]++;
}
inline void del(int x){
x = c[x];
sum[val[x]]--;
val[x]--;
}
int main(){
n = read(),m = read();
for(int i = 1;i <= n;++i) v[i] = read();
for(int i = 1;i < n;++i){
int x = read(),y = read();
G[x].push_back(y);G[y].push_back(x);
}
dfs(1,0);
// cout << "GG" << endl;
// for(int i = 1;i <= n;++i) printf("%d %d\n",l[i],r[i]);cout << "GG"  << endl;
bl = sqrt(n);
for(int i = 1;i <= n;++i) belong[i] = i / bl;
for(int i = 1;i <= m;++i){
int x = read();
q[i].li = l[x];
q[i].ri = r[x];
q[i].ko = read();
q[i].id = i;
}
sort(q + 1,q + m + 1,cmp);
int nowl = 1,nowr = 0;
for(int i = 1;i <= m;++i){
int L = q[i].li,R = q[i].ri;
while(nowl > L) add(--nowl);
while(nowl < L) del(nowl++);
while(nowr < R) add(++nowr);
while(nowr > R) del(nowr--);
ans[q[i].id] = sum[q[i].ko];
}
for(int i = 1;i <= m;++i) printf("%d\n",ans[i]);
return 0;
}
```

### [luoguP4396 [AHOI2013]作业](https://www.luogu.org/problemnew/show/P4396)

这道题和上几道不大一样,发现不了区间限制之外,还有值域的限制

我们就用树状数组维护值域

但是这样的时间复杂度为$n\sqrt{n}logn$

其实跑$10^5$是挺虚的,还好题目$3s$,这道题有$n\sqrt{n}$的做法,先咕一咕

```cpp
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N = 1e5 + 3;
int a[N],ans[N],belong[N];
int ans2[N],ans1[N];
int n,m,bl;
int maxx;
int book[N];
struct Q{
int li,ri;
int ai,bi;
int id;
}q[N << 1];
struct BIT{
int c[N << 3];
inline void add(int x,int v){
for(;x <= maxx;x += x & -x) c[x] += v;
}
inline int query(int x){
int res = 0;
for(;x;x -= x & -x) res += c[x];
return res;
}
};
BIT T1,T2;
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline bool cmp(Q x,Q y){
return belong[x.li] ^ belong[y.li] ? belong[x.li] < belong[y.li] : belong[x.li] & 1 ? x.ri < y.ri : x.ri > y.ri;
}
//int ans = 0;
inline void add(int x){
x = a[x];
if(book[x] == 0) T2.add(x,1);
book[x]++;
T1.add(x,1);
}
inline void del(int x){
x = a[x];
if(book[x] == 1) T2.add(x,-1);
book[x]--;
T1.add(x,-1);
}
int main(){
n = read(),m = read();
bl = sqrt(n);
for(int i = 1;i <= n;++i) {
a[i] = read() + 1,belong[i] = i / bl;
maxx = max(maxx,a[i]);
}
for(int i = 1;i <= m;++i){
q[i].li = read(),q[i].ri = read();
q[i].ai = read() + 1,q[i].bi = read() + 1;
if(q[i].ai > q[i].bi) swap(q[i].ai,q[i].bi);
q[i].id = i;
maxx = max(q[i].ai,max(maxx,q[i].bi));
}
sort(q + 1,q + m + 1,cmp);
int nowl = 1,nowr = 0;
for(int i = 1;i <= m;++i){
int L = q[i].li,R = q[i].ri;
while(nowl > L) add(--nowl);
while(nowr < R) add(++nowr);
while(nowl < L) del(nowl++);
while(nowr > R) del(nowr--);
ans1[q[i].id] = T1.query(q[i].bi) - T1.query(q[i].ai - 1);
ans2[q[i].id] = T2.query(q[i].bi) - T2.query(q[i].ai - 1);
}
for(int i = 1;i <= m;++i) printf("%d %d\n",ans1[i],ans2[i]);
return 0;
}
```