普通莫队
普通莫队
形式
如果从\([l,r]\) 的答案能够$ O(1)$扩展到 \([l+1,r][l-1,r][l,r+1][l,r-1]\)(即与\([l,r]\)相邻的区间)的答案,那么使用莫队算法可以在\(O(n\sqrt n)\)的复杂度内求出所有询问的答案。
核心
我们假设已经知道\([l,r]\)的答案,现在我们要求\([l',r']\),我们就可以移动左右指针,从而计算出答案
但是这可以被卡到\(O(n)\)
因此我们考虑进行离线处理
我们对\(l\)的数值进行分块,一共分成\(\sqrt n\)段,每一段的编号相同
第一关键字是段的编号,第二关键字是右端点的编号
然后我们就可以暴力了
时间复杂度
前提条件:m与n同阶
实际上莫队我们可以理解为左端点牺牲了一定的有序,换取了时间复杂度的优化
如果我们严格按照左端点不降,相同时比较右端点,这时如果相邻两个右端点相差n时,时间复杂度就是\(O(n^2)\)
如果我们用分块的思想,将一定量的左端点笼统的分为一类,比如我们每\(\sqrt {n}\)分为一块,块内的按照右端点排序
这样对于每一个新块进行第一次计算,时间复杂度相当于\(O(n)\),总时间为\(O(n\sqrt {n})\)
对于块内后续的计算,我们的右端点已经有序,时间复杂度相当于\(O(n)\),总时间为\(O(n\sqrt {n})\)
但这时我们的左端点并不是有序的,我们设每一块内的左端点的最大值为\([max_1,max_2,...,max_{(\sqrt {n})}]\)
我们每一次最多可能移动\(max_i-max_{i-1}\)次
因此我们有
综上,时间复杂度为\(O(n\sqrt{n})\)
参考普通莫队算法 - OI Wiki (oi-wiki.org)
细节问题
1,\(l\)要为1,\(r\)要为0
2,分块时注意第一关键字,分不好就会TLE
3,询问间区间的转移,我们最好先扩大区间,再缩小区间,避免出现问题
2339 Toys "R" Us - PCOI Online Judge (pcoij8.ddns.net)
题目大意
询问\([l,r]\)中第一个没有出现的正整数
做法
一眼莫队,对于寻找第一个没出现的正整数,我们考虑用set查询
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int cnt[maxn],a[maxn],ans[maxn];
set<int>Set;
int n,m,l,size;
struct node{
int x,y,id;
}q[maxn];
bool cmp(node x,node y){
if(x.x/size==y.x/size)return x.y<y.y;
return x.x/size<y.x/size;
}
void update(int now,int whi){
if(whi==1){
cnt[a[now]]++;
if(cnt[a[now]]==1){
Set.erase(a[now]);
}
}
else{
cnt[a[now]]--;
if(!cnt[a[now]])
Set.insert(a[now]);
}
}
int main(){
for(int i=1;i<=100001;i++)Set.insert(i);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].x,&q[i].y);
q[i].id=i;
}
size=sqrt(m);
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int ql=q[i].x,qr=q[i].y;
while(r<qr)update(++r,1);
while(r>qr)update(r--,-1);
while(l>ql)update(--l,1);
while(l<ql)update(l++,-1);
ans[q[i].id]=*Set.begin();
}
for(int i=1;i<=m;i++){
printf("%d\n",ans[i]);
}
return 0;
}
DQUERY - D-query - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目大意
求区间内不同的数的个数
做法
板子
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3*1e4+5,maxm=1e6+5;
int size,n,m,num[maxn],d[maxm],NUM,ans[maxm];
struct node{
int x,y,id;
}a[maxm];
bool cmp(node x,node y){
if(x.x/size==y.x/size)return x.y<y.y;
return x.x<y.x;
}
void update(int now){
if(!d[num[now]])NUM++;
d[num[now]]++;
}
void del(int now){
d[num[now]]--;
if(!d[num[now]])NUM--;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>num[i];
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y;
a[i].id=i;
}
size=sqrt(n);
sort(a+1,a+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int L=a[i].x,R=a[i].y;
while(r+1<=R)update(++r);
while(L<=l-1)update(--l);
while(r>R)del(r--);
while(l<L)del(l++);
ans[a[i].id]=NUM;
}
for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
return 0;
}
[P1494 国家集训队] 小 Z 的袜子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
做法
和前面类似,改一下update和del就可以了
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5*1e4+5;
int size=0,num[maxn],tot,d[maxn],n,m;
pair<int,int>ans[maxn];
struct node{
int x,y,id;
}a[maxn];
bool cmp(node x,node y){
if(x.x/size==y.x/size)return x.y<y.y;
return x.x/size<y.x/size;
}
void update(int now){
now=num[now];
tot-=d[now]*(d[now]-1)/2;
d[now]++;
tot+=d[now]*(d[now]-1)/2;
return ;
}
void del(int now){
now=num[now];
tot-=d[now]*(d[now]-1)/2;
d[now]--;
tot+=d[now]*(d[now]-1)/2;
return ;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>num[i];
}
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y;
a[i].id=i;
}
size=sqrt(n);
sort(a+1,a+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int L=a[i].x,R=a[i].y;
while(r+1<=R)update(++r);
while(L<=l-1)update(--l);
while(r>R)del(r--);
while(l<L)del(l++);
int A=tot,B=(R-L+1);
B=B*(B-1)/2;
if(B!=0){
int C=__gcd(A,B);
A/=C,B/=C;
}
else A=0,B=1;
ans[a[i].id]={A,B};
}
for(int i=1;i<=m;i++){
printf("%lld/%lld\n",ans[i].first,ans[i].second);
}
return 0;
}
P3709 大爷的字符串题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目大意
求区间中众数出现的次数
做法
莫队,我们先离散化,然后每一次记下该数出现的次数
我们再开一个数组记录出现i次的数的个数
对于扩大区间我们可以比较容易的更新答案
如果缩小区间,我们看一下减少的 这个数的 出现次数是不是最大,再判断出现次数最大的数 的个数是否只有一个,如果是的话答案就要减一
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2*1e5+5;
int size=0,num[maxn],tot,n,m,t[maxn],d[maxn];
map<int,int>mp;
int ans[maxn],ANS;
struct node{
int x,y,id;
}a[maxn];
bool cmp(node x,node y){
if(x.x/size==y.x/size)return x.y<y.y;
return x.x/size<y.x/size;
}
void update(int now){
now=num[now];
d[now]++;
t[d[now]-1]--;
t[d[now]]++;
ANS=max(ANS,d[now]);
return ;
}
void del(int now){
now=num[now];
if(d[now]==ANS&&t[d[now]]==1)ANS--;
t[d[now]-1]++;
t[d[now]]--;
d[now]--;
return ;
}
signed main(){
cin>>n>>m;
t[0]=n;
for(int i=1;i<=n;i++){
cin>>num[i];
mp[num[i]]=1;
}
int len=0;
for(map<int,int>::iterator it=mp.begin();it!=mp.end();it++)it->second=++len;
for(int i=1;i<=n;i++){
num[i]=mp[num[i]];
}
for(int i=1;i<=m;i++){
cin>>a[i].x>>a[i].y;
a[i].id=i;
}
size=sqrt(n);
sort(a+1,a+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int L=a[i].x,R=a[i].y;
while(r+1<=R)update(++r);
while(L<=l-1)update(--l);
while(r>R)del(r--);
while(l<L)del(l++);
// cout<<L<<" "<<R<<" "<<ANS<<endl;
ans[a[i].id]=ANS;
}
for(int i=1;i<=m;i++)cout<<-ans[i]<<endl;
return 0;
}
带修改莫队
普通莫队是不能带修改的。
我们可以强行让它可以修改,就像 DP 一样,可以强行加上一维 时间维, 表示这次操作的时间。
时间维表示经历的修改次数。
即把询问\([l,r]\)变成\([l,r,time]\)
时间复杂度
我们设序列长为n,m个询问,t个修改。
带修莫队排序的第二关键字是右端点所在块编号,不同于普通莫队。
想一想,如果不把右端点分块:
- 乱序的右端点对于每个询问会移动n次。
- 有序的右端点会带来乱序的时间,每次询问会移动t次。
当n,m,t同阶的时候,时间复杂度我们可以看作\(O(n^{\frac{5}{3}})\)
我们一般使用\(n^{\frac{2}{3}}\)来当块长
具体地参考:带修改莫队 - OI Wiki (oi-wiki.org)
[P1903 国家集训队] 数颜色 / 维护队列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目大意
找区间中有多少个不同的数,有修改操作
做法
我们开两个数组,第一个是记录询问,第二个是记录修改
我们每遇到一个修改操作,我们就把时间戳加一
我们按左端点块的编号,右端点块的编号,时间戳来排序
我们查询时,先按照前面一样,把指针指向现在的l,r,然后我们再在时间轴上跳,
如果修改的数不在区间内,那就直接修改,否则我们要更新一下答案
写代码的时候,我们可以记录修改的位置与数,当我们修改的时候,我们直接把数和原数组相应的位置swap一下就可以了
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=133333+5,maxm=1e6+5;
int size;
int f[maxn],d[maxm],ANS=0,n,m,B,C,lena=0,ans[maxn];
char A;
struct node1{
int x,y,t,id;
}a[maxn];
struct node2{
int pos,num;
}b[maxn];
bool cmp(node1 x,node1 y){
if(x.x/size==y.x/size){
if(x.y/size==y.y/size)return x.t<y.t;
return x.y<y.y;
}
return x.x<y.x;
}
void update(int now){
now=f[now];
if(!d[now])ANS++;
d[now]++;
}
void del(int now){
now=f[now];
d[now]--;
if(!d[now])ANS--;
}
void change(int now,int l,int r){
if(l<=b[now].pos&&b[now].pos<=r){
del(b[now].pos);
swap(b[now].num,f[b[now].pos]);
update(b[now].pos);
}
else swap(b[now].num,f[b[now].pos]);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>f[i];
}
int T=0;
for(int i=1;i<=m;i++){
cin>>A>>B>>C;
if(A=='Q'){
if(B>C)swap(B,C);
++lena;
a[lena]={B,C,T,lena};
}
else{
b[++T]={B,C};
}
}
size=pow(n,double(2)/double(3));
sort(a+1,a+lena+1,cmp);
int l=1,r=0;T=0;
for(int i=1;i<=lena;i++){
int L=a[i].x,R=a[i].y,Time=a[i].t;
// cout<<L<<" "<<R<<" "<<Time<<endl;
while(r+1<=R)update(++r);
while(L<=l-1)update(--l);
while(R<r)del(r--);
while(l<L)del(l++);
// cout<<ANS<<endl;
while(T+1<=Time)change(++T,l,r);
while(Time<T)change(T--,l,r);
ans[a[i].id]=ANS;
// cout<<ANS<<endl;
}
for(int i=1;i<=lena;i++)cout<<ans[i]<<endl;
return 0;
}

浙公网安备 33010602011771号