莫队模板
基础莫队:
例题:HH的项链以及题解链接,这题卡莫队,但是通过这个题了解基础莫队是不错的.
莫队所含的最基础及最核心的暴力思想,是根据前一个查询区间结果扩展下一个查询区间答案.
我们有两个指针指向当前区间左右界,同时用res存储目前结果,然后不断移动指针靠近当前查询区间,动态更新答案.
对这个题来说,若我们遇到了一个未出现的种类,就给它res++,如何判断它出现没出现过?cnt[N],表示一个种类出现的次数.
N比较小,支持我们瞎搞,当cnt[种类]=0,就res++.
反过来,当我们去掉一个种类时,要判断cnt[种类]=0,就res--;
这么一看,好像莫队接近纯暴力?
那你说得对就太天真了,这只是莫队的轻量化模型.
要让它变成莫队,我们就要离线处理,将所有查询排序,排序的目标是使指针移动次数最小化.
那么想想怎么做到这件事呢,有一种简单方法就是按照左界升序排列,左界相同时,所有右界按升序排列.
这样就完成了缺陷莫队算法.
毕竟还不是完全版本,肯定还有优化空间.想象一下如果每跨一个左界,右界在1~n之间反复横跳,可以想象到我们还是要T.
那么,我们可以对区间进行分块处理,每个块内部左界移动范围被限制到sqrt(n)级别,进行块内部的右界排序(1~n).
貌似鸡肋的排序,效果是比较显著的,这便是普通莫队.
既然是普通莫队,就还有优化空间,我们可以想象每个块处理完最后的右界都在N那种边缘位置上,这个时候其实我们将下一个块右界降序排序效果更佳.
所以这就是优化之一,奇偶排序.
另外就是其实还可以再优化,我们看每个块内部是否有左界存在,在这种基础上相邻有左界的块内部进行相反方向排序.
这样就可以实现更好的优化效果了.
#include<bits/stdc++.h>
#define qr qr()
using namespace std;
inline int qr
{
char ch=getchar();int x=0;bool f=1;
while(ch>57||ch<48)
{ if(ch=='-')f=0;
ch=getchar();
}while(ch<=57&&ch>=48)x=x*10+ch-48,ch=getchar();
if(!f)return ~(x-1);
return x;
}
const int N=1e6+30,MB=1e3+20;
struct line{
int l,r,id;
}edge[N];
// 品种出现次数↓ ↓均用于 ↓排序优化
int n,m,bl[N],cnt[N],len,p,vis[MB],sum[MB],ans[N],num[N],nl,nr,res;
// ↑存种类
bool cmp(line a,line b)
{
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:(sum[bl[a.l]]&1)?a.r>b.r:a.r<b.r;
}
//基础写法.
//void add(int st)
//{
// if(!cnt[num[st]])res++;
// ++cnt[num[st]];
//}
//void del(int st)
//{
// --cnt[num[st]];
// if(!cnt[num[st]])res--;
//}
//void ask(int l,int r)
//{
// while(nl<l)del(nl++);
// while(nl>l)add(--nl);
// while(nr<r)add(++nr);
// while(nr>r)del(nr--);
//}
//进阶写法
void ask(int l,int r)
{
while(nl<l)res-=!--cnt[num[nl++]];
while(nl>l)res+=!cnt[num[--nl]]++;
while(nr<r)res+=!cnt[num[++nr]]++;
while(nr>r)res-=!--cnt[num[nr--]];
}
void init()
{
n=qr;p=sqrt(n);len=n/p,nl=1,nr=1;
for(int i=1;i<=n;i++)num[i]=qr,bl[i]=i/p;
m=qr,cnt[num[1]]=1,res=1;
for(int i=1;i<=m;i++)edge[i].l=qr,vis[bl[edge[i].l]]=1,edge[i].r=qr,edge[i].id=i;
sum[0]=vis[0];
for(int i=1;i<=len;i++)sum[i]=sum[i-1]+vis[i];
sort(edge+1,edge+m+1,cmp);
for(int i=1;i<=m;i++)ask(edge[i].l,edge[i].r),ans[edge[i].id]=res;
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
int main()
{
init();
return 0;
}
带修莫队:
带修莫队,就是带修改的莫队.
它实现思路比较简单,既然我们要更改,就把所有修改的点给存起来作为时间戳存起来,将整个查询变成一个二维的莫队.
每个查询还是和普通的莫队里面一样,全部存储起来排序,然后离线保存结果,最后全部输出.
图示:
重点来了:莫队最大的核心特点是反复横跳,离线排序,那么怎么排呢?
我们将所有查询排序时用三个关键字,依次为l(左界)所在的块与r(右界)所在的块还有时间戳,排序.
(块的大小取
这里可以看出与普通莫队不同的是我们的右界也按照所在块从小到大排序了.
不知道这个能不能奇偶排序?身为蒟蒻,请求大佬指点.
例题传送门:数颜色/维护队列,这个题注意卡常!
#include<bits/stdc++.h>
#define ll long long
#define qr qr()
#define pr pr()
using namespace std;
//**的,它颜色范围是1~1e6,不是133333...
const int N=1e6+20;
struct mov{
int l,r,t,id;
}mv[N];
struct remove{
int st,pos;
}rem[N];
int n,m,p,num[N],bl[N],sum[N],all,ti,len,nl,nr,nt,res,ans[N];
int cmp(mov A,mov B)
{
return (bl[A.l]^bl[B.l])?A.l<B.l:(bl[A.r]^bl[B.r])?A.r<B.r:A.t<B.t;
}
inline int qr
{
int x=0;char ch=getchar();
while(ch>57||ch<48)ch=getchar();
while(ch<=57&&ch>=48)x=x*10+ch-48,ch=getchar();
return x;
}
void re(int now,int l,int r)
{//修改部分
if(rem[now].st>=l&&rem[now].st<=r)
{
if(!--sum[num[rem[now].st]])res--;
if(!sum[rem[now].pos]++)res++;
}swap(rem[now].pos,num[rem[now].st]);
//减少码量↑
}
void ask(int l,int r,int t)
{//查询部分
while(nl<l)res-=!--sum[num[nl++]];
while(nl>l)res+=!sum[num[--nl]]++;
while(nr<r)res+=!sum[num[++nr]]++;
while(nr>r)res-=!--sum[num[nr--]];
while(nt<t)re(++nt,l,r);
while(nt>t)re(nt--,l,r);
}
void init()
{
n=qr,m=qr,p=pow(n,0.66666),len=n/p;
for(int i=1;i<=n;i++)num[i]=qr;
char op;int l,r,st,pos;
for(int i=1;i<=m;i++)
{
op=getchar();
while(op!='Q'&&op!='R')op=getchar();
if(op=='Q')
{ l=qr,r=qr;
mv[++all]={l,r,ti,all};
}else
{ st=qr,pos=qr;
rem[++ti]={st,pos};
}
}for(int i=1;i<=n;i++)bl[i]=i/p;
sort(mv+1,mv+all+1,cmp);
for(int i=1;i<=all;i++)ask(mv[i].l,mv[i].r,mv[i].t),ans[mv[i].id]=res;
for(int i=1;i<=all;i++)printf("%d\n",ans[i]);
}
int main()
{
init();
return 0;
}
回滚莫队:
回滚莫队的思想基于莫队,我们依旧按查询左边界的区块去进行查询区间的排序,让同块内部的右边界升序/降序排列,剩下块内部分直接暴力解决.
它能够处理那种单向操作简单,回退困难的问题.
浅显易懂的例子就是,让你吃东西,比让你吐出来简单,那我们就避免吐这个操作.
说人话,就是对于查询区间来说减少/增加比另一操作更易实现,我们就一直让程序只执行其中之一.
那回归它的实现思路,我们进行排序之后,对于每个查询区间,右边界的操作同普通莫队一样一直扩展.
而左边界在区块内部的部分就直接暴力解决.
板子题链接
模板:
#include<bits/stdc++.h>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#define pa pair<int,int>
#define qr qr()
#define ll long long
using namespace std;
map<int,int> mp;
const int N=1e5+5000,MB=5000;
int num[N],n,q,p,bl[N],st[MB],ed[MB],len,nr,tot,start;
ll cnt[N],ans,out[N],mf[N];
struct node{
int id,l,r,lb;
}ask[N];
struct nd{
int nown;ll as;
}re[N];
inline int qr
{
int x=0;char ch=getchar();
while(ch>57||ch<48)ch=getchar();
while(ch>=48&&ch<=57)x=(x<<3)+(x<<1)+ch-48,ch=getchar();
return x;
}
bool cmp(node A,node B)
{
return (bl[A.l]^bl[B.l])?bl[A.l]<bl[B.l]:A.r<B.r;
}
void mo_q(int id,int l,int r)
{
while(nr<r)
{
int sn=num[++nr];
++cnt[sn];
ans=max(ans,cnt[sn]*mf[sn]);
}
ll res=ans;
int red=min(start,r);
int top=0;
for(int nl=l;nl<=red;++nl)
{
int sn=num[nl];
re[++top]={sn,cnt[sn]};
++cnt[sn];
res=max(res,cnt[sn]*mf[sn]);
}
for(int i=top;i;i--)
cnt[re[i].nown]=re[i].as;
out[id]=res;
}
void init()
{
n=qr,q=qr,p=sqrt(n);
int pos;
for(int i=1;i<=n;++i)
{
pos=qr;
if(!mp[pos])
{
mp[pos]=++tot;
mf[tot]=pos;
}num[i]=mp[pos];
}
int l,r;
len=n/p;
if(len*p!=n)++len;
st[1]=1,ed[1]=p;
for(int i=st[1];i<=ed[1];++i)bl[i]=1;
for(int nb=2;nb<=len;++nb)
{
st[nb]=st[nb-1]+p;
ed[nb]=ed[nb-1]+p;
for(int now=st[nb];now<=ed[nb];++now)
bl[now]=nb;
}
for(int i=1;i<=q;++i)
{
l=qr,r=qr;
ask[i]={i,l,r,bl[l]};
}
sort(ask+1,ask+q+1,cmp);
// for(int i=1;i<=q;++i)
// {
// printf("%d:%d(bl:%d,st:%d,ed:%d),%d\n",ask[i].id,ask[i].l,ask[i].lb,st[ask[i].lb],ed[ask[i].lb],ask[i].r);
// }
for(int i=1;i<=q;++i)
{
if(bl[ask[i].l]^bl[ask[i-1].l])
{
memset(cnt,0,sizeof(cnt));
start=ed[ask[i].lb];
nr=start;
ans=0;
}mo_q(ask[i].id,ask[i].l,ask[i].r);
}
for(int i=1;i<=q;++i)printf("%lld\n",out[i]);
}
int main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
init();
return 0;
}
4.22 update:
莫队在答案区间具有可加性的题中可以用值域分块,我在分块板块中进行了更新:分块板子(例题:作业).
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现