萌新一枚,借鉴了许多大佬而总结下的笔记,可能有瑕疵或者错误,望指正
1莫队是基于分块操作,对数据进行离线处理,不适用于强制在线的情况
2将询问根据块号排序,用左右指针l,r,进行移动,寻求每个询问的答案
一、普通莫队
1、首先关于询问排序,先按照左端点所在的块号排序,左端点处于同一块的询问按照右端点升序排序。以上排序有一个问题,左端点处于同一区间的询问虽然保证了右端点单调递增,(右指针查找的时候可以一直往前走),但是对于下一个区间就不行了,因此右指针还需要往回走,才能开始下一区间的查找。
2、因此,对于右端点的排序,让左端点同处于块号为奇数块的询问,右端点按照升序排序(照常),但对于偶数块的询问,右端点按照降序排序,(这样右指针往回走的时候也能查找答案,就不是浪费时间了)。
代码实现
1数据存储
int pos[maxn],len;//分块,分块的长度
int n,m;//n个数据,m个询问
int ans[maxn];//每次询问的答案
int a[maxn];//数据
int res;//当前答案
struct query{
int l;//左端点
int r;//右端点
int id; //第几次询问
}q[maxn];
2分块预处理
void init()//分块
{
len=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i+len-1)/len;
}
3排序方法
bool cmp(const query&a,const query&b)
{
if(pos[a.l]!=pos[b.l])//左端点不在同一块内,按左端点的分块排序
return pos[a.l]<pos[b.l];
//左端点处于同一个分块
if(pos[a.l]%2)return a.r<b.r;//左端点分块为奇数时,右端点从小到大排序
return a.r>b.r;//为偶数时,右端点从大到小排序
}
4进行莫队处理询问
inline void add(int x)//常规莫队加操作(按照情况灵活规定)
{
}
inline void del(int x)//常规莫队减操作(按照情况灵活规定)
{
}
void moqueue()
{
int l=1,r=0,now=0;//当前左端点,右端点,以及进行了几次修改
for(int i=1;i<=qcnt;i++)
{
//常规莫队操作
while(l>q[i].l)add(a[--l]);
while(r<q[i].r)add(a[++r]);
while(l<q[i].l)del(a[l++]);
while(r>q[i].r)del(a[r--]);
ans[q[i].id]=res;//记录答案
}
}
5完整代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;
int pos[maxn],len;//分块,分块的长度
int n,m;//n个数据,m个询问
int ans[maxn];//每次询问的答案
int a[maxn];//数据
int res;//当前答案
struct query{
int l;//左端点
int r;//右端点
int id; //第几次询问
}q[maxn];
void init()//分块
{
len=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i+len-1)/len;
}
bool cmp(const query&a,const query&b)
{
if(pos[a.l]!=pos[b.l])//左端点不在同一块内,按左端点的分块排序
return pos[a.l]<pos[b.l];
//左端点处于同一个分块
if(pos[a.l]%2)return a.r<b.r;//左端点分块为奇数时,右端点从小到大排序
return a.r>b.r;//为偶数时,右端点从大到小排序
}
inline void add(int x)//常规莫队加操作(按照情况灵活规定)
{
}
inline void del(int x)//常规莫队减操作(按照情况灵活规定)
{
}
void moqueue()
{
int l=1,r=0,now=0;//当前左端点,右端点,以及进行了几次修改
for(int i=1;i<=m;i++)
{
//常规莫队操作
while(l>q[i].l)add(a[--l]);
while(r<q[i].r)add(a[++r]);
while(l<q[i].l)del(a[l++]);
while(r>q[i].r)del(a[r--]);
ans[q[i].id]=res;//记录答案
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int m;
cin>>n>>m;//n个数据,m次动作
init();//分块
for(int i=1;i<=n;i++)
cin>>a[i];
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);//询问排序
moqueue();//莫队处理
for(int i=1;i<=m;i++)//输出答案
cout<<ans[i]<<'\n';
}
二、回滚莫队
1回滚莫队一般用于处理进行收缩(del操作)数据难维护的问题,比如维护一个区间的最大值,当区间收缩时,最大值用普通莫队很难维护。
2当询问左右端点处于同一个区间,直接暴力计算即可
3回滚莫队不再进行奇分块右端点升序,偶分块降序的形式,让左端点处于同一分块的询问的右端点严格递增,让右端点处于当前块(左端点处在的块)右端点处,左端点处于右端点加1的位置。的在查询过程中,先将右指针移动到右端点,记录一个存档,再去移动左指针,左指针移到左端点时记录此次询问答案,再将左指针移回原位,不需要去处理图中del操作,移回来时直接读取之前记录的存档,类似于实现了撤销操作
代码实现
1数据存储
int pos[maxn],len;//分块,分块的长度
int n,m;//n个数据,m个询问
int ans[maxn];//每次询问的答案
int a[maxn];//数据
int res;//当前答案
struct query{
int l;//左端点
int r;//右端点
int id; //第几次询问
}q[maxn];
2分块预处理
void init()//分块
{
len=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i+len-1)/len;
}
3排序方法
bool cmp(const query&a,const query&b)
{
if(pos[a.l]!=pos[b.l])//左端点不在同一块内,按左端点的分块排序
return pos[a.l]<pos[b.l];
//左端点处于同一个分块
return a.r<b.r;//右端点从小到大排序
}
4进行莫队处理询问
inline void add(int x)//常规莫队加操作(按照情况灵活规定)
{
}
inline void del(int x)//常规莫队减操作(按照情况灵活规定)
{
}
void moqueue()
{
int l=1,r=0,now=0;//当前左端点,右端点,以及进行了几次修改
int last =-1;//记录前一次的分块区号
for(int i=1;i<=m;i++)
{
if(pos[q[i].l]==pos[q[i].r])//左右端点处于同一个分块,直接暴力
{
int res2=0;//记录当前暴力所获得的的答案
for(int j=q[i].l;j<=q[i].r;j++)
;//根据情况灵活规定
ans[q[i].id]=res2;//记录答案
for(int j=q[i].l;j<=q[i].r;j++)
;//根据上面的查找,消除其影响
continue;
}
if(pos[q[i].l]!=last)//当前询问的左端点所在的分块和之前不同
{
int R=min(n,pos[q[i].l]*len);//寻找当前询问左端点处在的分块的最右端点
while(r>R) del(a[r--]);
while(l<R+1) del(a[l++]);//将左指针移动到R+1的位置,右指针移动到R的位置
last=pos[q[i].l];
res=0;//答案清零
}
int templ=l;
while(r<q[i].r)add(a[++r]);
int temp=res;//将当前答案存档
while(templ>q[i].l)//移动临时左指针
add(a[--templ]);
ans[q[i].id]=res;
res=temp;//读档
while(templ<l)del(a[templ++]);//再将临时左指针移回来,消除影响
}
}
5完整代码
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int m;
cin>>n>>m;//n个数据,m次动作
init();//分块
for(int i=1;i<=n;i++)
cin>>a[i];
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);//询问排序
moqueue();//莫队处理
for(int i=1;i<=m;i++)//输出答案
cout<<ans[i]<<'\n';
}
三、带修改莫队
1一般只适用于单点修改
2询问和修改操作分开来存储,询问结构体中添加一个新的元素pre,用于记录在此次询问前进行了几次修改。排序方法也跟原来不同,先根据左端点所在的分块排序,左端点处于同一分块是根据右端点所在的分块排序,右端点也处于同一分块时根据此次询问前进行的修改的数量升序排序。
3在左右指针移动查找答案是,add和del操作和之前一样,再次基础上添加了修改操作,假设有一修改操作,将位置pos上的值x改成y,先判断当前修改是否对本次询问有影响,从而更改当前答案res,修改完成后,本次被修改的值x变成y,而y的值变成x,这样就可以进行撤销倒退操作,消除本次修改影响。
代码实现
1数据存储
int pos[maxn],len;//分块,分块的长度
int n;
int ans[maxn];//每次询问的答案
int a[maxn];//数据
struct query{
int l;//左端点
int r;//右端点
int pre;//在此次询问之前进行了几次修改
int id;
}q[maxn];
struct change{
int pos,x;//修改的位置,以及修改的值
}c[maxn];//记录修改
int ccnt;//修改的编号
int qcnt;//询问的编号
int res;//当前答案
2分块预处理
void init()//分块
{
len=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i+len-1)/len;
}
3排序方法
bool cmp(const query&a,const query&b)
{
if(pos[a.l]!=pos[b.l])//左端点不在同一块内,按左端点的分块排序
return pos[a.l]<pos[b.l];
if(pos[a.r]!=pos[b.r])//右端点不在同一块内,按右端点的分块排序
return pos[a.r]<pos[b.r];
if(a.pre!=b.pre)//左右端点都属于同一块,按在此之前完成的修改进行排序
return a.pre<b.pre;
}
4进行莫队处理询问
inline void add(int x)//常规莫队加操作(按照情况灵活规定)
{
}
inline void del(int x)//常规莫队减操作(按照情况灵活规定)
{
}
void update(int now,int i)//修改操作的编号,询问的编号
{
if(c[now].pos<=q[i].r&&c[now].pos>=q[i].l)//如果修改的该点对本次询问有影响,具体按照情况灵活规定
{
}
swap(c[now].x,a[c[now].pos]);//将修改的值和被修改的值互换,这样之后还能换回来,(恢复成修改前的样子)
}
void moqueue()
{
int l=1,r=0,now=0;//当前左端点,右端点,以及进行了几次修改
for(int i=1;i<=qcnt;i++)
{
//常规莫队操作
while(l>q[i].l)add(a[--l]);
while(r<q[i].r)add(a[++r]);
while(l<q[i].l)del(a[l++]);
while(r>q[i].r)del(a[r--]);
//修改操作
while(now<q[i].pre)update(++now,i);
while(now>q[i].pre)update(now--,i);
ans[q[i].id]=res;//记录答案
}
}
5完整代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;
int pos[maxn],len;//分块,分块的长度
int n;
int ans[maxn];//每次询问的答案
int a[maxn];//数据
int ccnt;//修改的编号
int qcnt;//询问的编号
int res;//当前答案
struct query{
int l;//左端点
int r;//右端点
int pre;//在此次询问之前进行了几次修改
int id;
}q[maxn];
struct change{
int pos,x;//修改的位置,以及修改的值
}c[maxn];//记录修改
void init()//分块
{
len=sqrt(n);
for(int i=1;i<=n;i++)
pos[i]=(i+len-1)/len;
}
bool cmp(const query&a,const query&b)
{
if(pos[a.l]!=pos[b.l])//左端点不在同一块内,按左端点的分块排序
return pos[a.l]<pos[b.l];
if(pos[a.r]!=pos[b.r])//右端点不在同一块内,按右端点的分块排序
return pos[a.r]<pos[b.r];
if(a.pre!=b.pre)//左右端点都属于同一块,按在此之前完成的修改进行排序
return a.pre<b.pre;
}
inline void add(int x)//常规莫队加操作(按照情况灵活规定)
{
}
inline void del(int x)//常规莫队减操作(按照情况灵活规定)
{
}
void update(int now,int i)//修改操作的编号,询问的编号
{
if(c[now].pos<=q[i].r&&c[now].pos>=q[i].l)//如果修改的该点对本次询问有影响,具体按照情况灵活规定
{
}
swap(c[now].x,a[c[now].pos]);//将修改的值和被修改的值互换,这样之后还能换回来,(恢复成修改前的样子)
}
void moqueue()
{
int l=1,r=0,now=0;//当前左端点,右端点,以及进行了几次修改
for(int i=1;i<=qcnt;i++)
{
//常规莫队操作
while(l>q[i].l)add(a[--l]);
while(r<q[i].r)add(a[++r]);
while(l<q[i].l)del(a[l++]);
while(r>q[i].r)del(a[r--]);
//修改操作
while(now<q[i].pre)update(++now,i);
while(now>q[i].pre)update(now--,i);
ans[q[i].id]=res;//记录答案
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int m;
cin>>n>>m;//n个数据,m次动作
init();//分块
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=m;i++)
{
int op;
cin>>op;
if(op==1)//询问操作
{
qcnt++;
cin>>q[qcnt].l>>q[qcnt].r;
q[qcnt].pre=ccnt;
q[qcnt].id=qcnt;
}
else//修改操作
{
ccnt++;
cin>>c[ccnt].pos>>c[ccnt].x;
}
}
sort(q+1,q+1+qcnt,cmp);//询问排序
moqueue();//莫队处理
for(int i=1;i<=qcnt;i++)//输出答案
cout<<ans[i]<<'\n';
}