最近一段时间不知道学什么,索性就按着学长的课件查漏补缺,补充一下自己以前漏掉的知识点
莫队这个优秀的算法还是我们CJ学长莫涛队长发明的哩
普通莫队
先上道例题
给出一个长度为n 的数列,q次询问,每次询问一个区间中多少种不同的数字
这题显然可以用其他区间问题的数据结构来做,比如树状数组啥的
原题链接
但是!!!
为什么不 (问问神奇海螺呢) 试试暴力呢
让我们一起来分析一下暴力的写法:
每次遍历询问区间,统计出答案
那让我们想想,为什么这样的暴力跑不过呢?
因为询问的区间有很多重复的片段,导致某些点不断被遍历,大大减缓了效率
那我们是不是可以优化这个暴力???
当然
既然有很多重复的片段,那不是只要记录这个片段里保留的值就好了吗???
那我们该怎样在保证正确性的同时来记录这个值呢?
我们可以直接扩张或减小上一个询问区间来求得当前区间的值嘛,
这样就可以少算一部分重复的区间了
可是,这样的复杂度还是不够优秀,很容易被卡成O(N*Q)
想想为什么会被卡成O(N*Q)呢?
因为上一个询问区间和当前询问区间的两端点隔得太远,导致还是相当于几乎遍历了整个数组
所以
我们只要把上一个区间和当前区间的端点距离变近就好了!
所以把所有询问区间先排序 (这也是为什么普通莫队处理不了待修改的问题)
然后在按着询问来扫
那么我们剩下的问题就是在按照什么顺序来排序了
假如直接就按端点双关键字排序
那么在最坏的情况下就会: 左端点只遍历一遍数组,右端点却要每次询问都遍历一遍区间,那么又是O(N*Q)
所以我们不妨运用分块的思想, 将左端点按照所属的块来排序,右端点正常排序
// 也就是这样
int cmp(block a,block b)
{
if(a.l/sqrt(n)==b.l/sqrt(n)) return a.r<b.r;
return a.l<b.l; //已经保证了不在同一个块内,不用再除以sqrt(n)了
}
为什么这样复杂度是对的呢?
让我们分别考虑左右端点的移动情况:
左端点 : 每次跨越在一个块或者两个块,有Q次移动,就是O(Q*sqrt(N));
右端点 : 由于是在块内排序,所以每个块最多把整个序列遍历一遍,就是O(N*(sqrt(N)))
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define get getchar()
#define in inline
in ll read()
{
ll t=0; char ch=get;
while(ch<'0' || ch>'9') ch=get;
while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get;
return t;
}
const int _=1e6+6;
int n;
ll m,a[_],piece,vis[_],ans=0,answer[_];
struct block{
int l,r,id;
}q[_];
in int cmp(block a,block b)
{
if(a.l/piece == b.l/piece)
return a.r<b.r;
return a.l<b.l;
}
in void del(int x)
{
vis[x]--;
if(vis[x]==0)ans--;
}
in void add(int x)
{
if(vis[x]==0)ans++;
vis[x]++;
}
int main()
{
n=read();
for(re int i=1;i<=n;i++)
a[i]=read();
m=read();
for(re int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
piece=sqrt(n);
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(re int i=1;i<=m;i++)
{
while(l<q[i].l) del(a[l]),l++;
while(l>q[i].l) add(a[l - 1]),l--;
while(r<q[i].r) add(a[r + 1]),r++;
while(r>q[i].r) del(a[r]),r--;
answer[q[i].id]=ans;
}
for(re int i=1;i<=m;i++)
cout<<answer[i]<<endl;
}
带修改莫队
又是一道例题
Luogu P1903 数颜色
给定一个长为 (n) 的序列,与m个操作,一种为单点修改,一种为询问区间中有多少个不同的数字,T组询问
思路
看到这个题面,想想可以用哪些方法来解决?
刚刚我们不是学了莫队吗,那我们想想如何用莫队来解决这个问题
首先
之前我们学到了,莫队只适合在无修改且可离线的情况下使用
那我们有什么办法在莫队中加入修改操作呢???
我们一起来想想普通莫队为什么不支持修改操作,
显然,是因为莫队需要排序,而排序之后修改操作的顺序就乱了.
然后
为何不记录一下每次询问前是第几个修改操作呢?
并且在排序时把这个 "时间戳" 作为第三关键字
in int cmp(qu a,qu b)
{
if((a.l-1)/block==(b.l-1)/block) // block为莫队中每一个询问块的大小
{
if((a.r-1)/block==(b.r-1)/block)return a.t<b.t; // t为时间戳
return a.r<b.r;
}
else return a.l<b.l;
}
每次询问都相应的更新或恢复之前的值
// 莫队单点修改函数
void change(int i,int l,int r,bool flag) //第i个修改操作,当前询问区间 L~R
{ // flag为0表示当前位置需要修改为操作要求的值,为1表示需要恢复成此次操作之前的样子
int x=modify[i].x;
if(x>=l&&x<=r)
del(x); //把当前颜色对答案的影响删掉
c[x]=flag?modify[i].now:modify[i].last;
if(x>=l&&x<=r)
add(x); //把修改后的颜色对答案的影响加入
}
附上此题完整代码 YZHX太胖了,常数也大,要开O2才能过:
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
int t=0; char ch=get;
while(ch<'0' || ch>'9') ch=get;
while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get;
return t;
}
const int _=1e5+5;
struct qu{
int l,r,t,id;
}a[_]; // 询问操作
struct mo{
int x,now,last;
}modify[_]; //修改操作, now是修改前的数字,last是修改后的,x记录修改位置
int n,m,sum[_*10],c1[_],c[_],qtot,rtot,block,ans,s[_];
in void add(int x) //加入
{
sum[c[x]]++;
if(sum[c[x]]==1)ans++;
}
in void del(int x) //删除
{
sum[c[x]]--;
if(sum[c[x]]==0)ans--;
}
in int cmp(qu a,qu b) //排序顺序
{
if((a.l-1)/block==(b.l-1)/block)
{
if((a.r-1)/block==(b.r-1)/block)return a.t<b.t;
return a.r<b.r;
}
else return a.l<b.l;
}
in void change(int i,int l,int r,bool flag) //修改
{
int x=modify[i].x;
if(x>=l&&x<=r)
del(x);
c[x]=flag?modify[i].now:modify[i].last;
if(x>=l&&x<=r)
add(x);
}
int main()
{
n=read(),m=read();
block=sqrt(n);
for(re int i=1;i<=n;i++)
c1[i]=c[i]=read(); //需要 c1 数组记录序列初始状态
for(re int i=1;i<=m;i++)
{
char ch=get;
while(ch!='Q'&&ch!='R')ch=get;
int x=read(),y=read();
if(ch=='Q') a[++qtot].l=x,a[qtot].r=y,a[qtot].t=rtot,a[qtot].id=qtot;
else modify[++rtot].x=x,modify[rtot].now=c[x],modify[rtot].last=y,c[x]=y;
}
for(re int i=1;i<=n;i++) c[i]=c1[i];
sort(a+1,a+qtot+1,cmp);
int l,r,tnow;
l=a[1].l,r=a[1].r,tnow=a[1].t;
for(re int i=l;i<=r;i++)
add(i);
for(re int i=1;i<=tnow;i++)
change(i,l,r,0);
s[a[1].id]=ans;
for(re int i=2;i<=qtot;i++)
{
while (l<a[i].l) del(l++);
while (l>a[i].l) add(--l);
while (r<a[i].r) add(++r);
while (r>a[i].r) del(r--);
while (tnow<a[i].t) change(++tnow,l,r,0);
while (tnow>a[i].t) change(tnow--,l,r,1);
s[a[i].id]=ans;
}
for(re int i=1;i<=qtot;i++)
cout<<s[i]<<endl;
}