(笔记)莫队 回滚莫队 树上莫队
算法特点:
\(O(n\sqrt n)\),离线,多数时候需要一点卡常寄巧(但其时间复杂度在完全随机的数据下还是较优秀的)(?),不能做强制在线(这不废话吗),以根号分块为基础,主体为指针操作。
基础莫队
rt,以前是可以用莫队做的,但是现在加入了加强数据,好像不行了。
所以我们找了一题一样但是没有卡常的。
题意大概是给定\(A[1]\)~\(A[N]\),每次有询问\(l,r\),问区间\([l,r]\)中有多少个不同的数。
1.分块
块长为\(\sqrt n\),对询问左端点分块后对操作进行排序,第一关键字为操作左端点块的升序编号,第二关键字为操作右端点降序。
2.主体
对本题,令\(cnt[i]\)表示\(i\)出现的次数。使左指针\(le\)初始为\(1\),右指针\(ri\)为\(0\)。对排好序的操作进行指针对齐,由于之前排好序,所以在\(m,n\)同阶时,其时间复杂度为\(O(n\sqrt n)\)。
形象化即分块后右端点是有序的,每个块最多执行\(n\)次收缩操作,由\(\sqrt n\)块得最多进行\(n\sqrt n\)。
考虑左端点,每次在块内移动距离不超过\(\sqrt n\), 共\(m\)次移动,共移动\(m\sqrt n\)次,垮块的移动最多移动距离\(2\sqrt n\),最多垮\(\sqrt n\)次,故左端点共移动\(m\sqrt n + 2n\)次 ,即\(O(m\sqrt n)\)。
总体时间复杂度在两者同阶时即为\(O(n\sqrt n)\)
除此,当\(m,n\)不同阶时,使块长为\(\sqrt\frac{n^2}{m}\)时较优,具体证明可以通过式子\(\frac{n^2m}{s}=mn^2\)用基本不等式最小值推出\(ms=\frac{n^2}{s}\)时最小,整理上式可以得到\(s=\sqrt\frac{n^2}{m}\)
代码贴贴:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,a[N],ans[N],bs,cnt[N],temp,b[N];
struct Q{
int l,r,id;
}q[N];
bool cmp(Q x,Q y){
return b[x.l]==b[y.l] ? x.r<y.r:b[x.l]<b[y.l];
}
void add(int x){
cnt[a[x]]++;
if(cnt[a[x]]==1)temp++;
}
void del(int x){
cnt[a[x]]--;
if(cnt[a[x]]==0)temp--;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
bs=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=(i-1)/bs+1;
}
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r)add(++r);
while(r>q[i].r)del(r--);
while(l<q[i].l)del(l++);
while(l>q[i].l)add(--l);
ans[q[i].id]=temp;
}
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
return 0;
}
回滚莫队
用于解决:
只减不加(如\(mex\)值,区间内未出现过的最小非负整数),只加不减(如区间最大值)
具体前者比后者要复杂,但总体思想相同。
例题
只加不减歴史の研究
其余待更新