莫队算法

继分块后的第三种高级数据结构,,,
(学了分块后好像就是对莫队有了很高很高的兴趣,,估计是学分块学傻了吧 0.0)

还是先听了听大佬的课,用了一个小时自己消化了一下,才知道莫队的思想:就是在分块的基础上加上排序,可以大大降低复杂度,降至O(n1.5),还有一个最好认识的标志离线询问(那个分块9要是不在线就是个裸莫队啊 ̄へ ̄)
有关排序的证明,请参考这位大佬的介绍:传送门

莫队总结:

莫队算法通常用来解决序列上多次进行区间询问的问题。与分块算法一样,莫队算法常使用在,需要维护的信息无法快速合并的时候(即传统数据结构题无法简单处理)。使用莫队算法时,题目需要满足下列条件:
1、题目允许离线
2、题目没有修改操作(后面会进行拓展,使得莫队能支持一些简单的修改操作,即带修莫队);
3、不同询问区间的答案可以快速地互相计算得出

莫队算法的核心是将所有的询问重新排序,按照这个新的顺序依次回答询问,并且回答每一个询问时会以上一个询问为基础。

更具体地,我们举个例子,若现在已知区间[L1 , R1 ] 的答案,则要利用这个值得到区间 [L2 , R2 ] 的答案,需要花费O(|L1 – L2| + |R1 – R2|) 的时间,则我们可以将询问看成是平面上的一个坐标(Li , Ri) ,并定义两个询问间转移的花费,为它们在二维平面上的曼哈顿距离(这个写法是n*sqtr(n),但是蒟蒻不会,如果有兴趣的话:传送门)。但是一般来说都是用分块写的。

相关复杂度的证明:

设序列长度为 n n n,询问次数为 m m m,块大小为 b l o c k block block
分类讨论:
L的移动:若下一个询问与当前询问的L所在的块不同,那么只需要经过最多 2 b l o c k 2block 2block步可以使得L成功到达目标。复杂度为: O ( m ∗ b l o c k ) O(m*block) O(mblock)
R的移动 R R R只有在 b e l o n g [ L ] belong[L] belong[L]相同时才会有序,对于每一个块,排序执行了第二关键字R,因为R是单调递增的,所以枚举完一个块,R最多移动n次,总共有 n / b l o c k n/block n/block个块。复杂度为: O ( n ∗ n / b l o c k ) O(n*n/block) O(nn/block)
显然最优情况下两者应取等,即 m ∗ b l o c k = n 2 / b l o c k m*block=n^2/block mblock=n2/block,当 b l o c k = n / s q r t ( m ) block=n/sqrt(m) block=n/sqrt(m)时,取得最优移动次数为 n ∗ s q r t ( m ) n*sqrt(m) nsqrt(m),所以莫队最优复杂度为: O ( n ∗ s q r t ( m ) ) O(n*sqrt(m)) O(nsqrt(m))

上题目:

一号板子题:

经典莫队(题解注释吧)
第一次刷codeforces上的题,(好激动啊~~~٩(๑>◡<๑)۶)
现放AC记录:在这里插入图片描述
AC代码:

#include
#include
#include
#define LL long long
using namespace std;
const int sea=1<<20;
int n,m,k,belong[sea],block,L=1,R=0,a[sea];
LL ans[sea],flag[sea],res=0;
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w; 
}
struct ask {int l,r,id;}q[sea];
bool cmp(ask x,ask y)
{
	if(belong[x.l]==belong[y.l]) return x.r<y.r;
	return belong[x.l]<belong[y.l];
}//重要的排序
void del(int x)//删除
{
	flag[a[x]]--;
	res-=flag[a[x]^k];
}
void add(int x)//添加
{
	res+=flag[a[x]^k];//每添加进去一个数就相当于加上这个异或k
	flag[a[x]]++;
}
int main()
{
	n=read(); m=read(); k=read();
	block=sqrt(n);
	for(int i=1;i<=n;i++) 
	{
		a[i]=read(); 
		a[i]=a[i]^a[i-1];//处理前缀和,至于为什么要处理前缀和,,应该都知道吧,就是一段区间i,j的异或就相当于从1异或到i-1的连续异或再异或上从1异或到j的连续异或;
		belong[i]=i/block;
	}
	for(int i=1;i<=m;i++)
	{
		q[i].l=read(); q[i].r=read(); 
		q[i].id=i;
	}
	sort(q+1,q+m+1,cmp);//莫队基本用法即核心思想
	flag[0]=1;//记录前缀和出现的此数 
	for(int i=1;i<=m;i++)
	{
		while(L<q[i].l) del(L-1),L++;
		while(L>q[i].l) L--,add(L-1);
		while(R<q[i].r) R++,add(R);
		while(R>q[i].r) del(R),R--;
		ans[q[i].id]=res;//这个比较抽象不是很好理解看下面我画个图帮助理解吧
 	}
 	for(int i=1;i<=m;i++)
 	cout<<ans[i]<<endl;
	return 0;
} 

所知区间:[ ]
询问区间:( )
L,R:所知区间左,右端点
l,r;询问区间左,右端点
莫队算法_第1张图片
这就比较清晰了吧,,(字太丑啦(⌒_⌒;)想着回去买个写字板什么的,,)

二号板子题:

Xors on Segments
(好好的一道经典莫队怎么被DP搞垮了,,,)
但是突然发现,这个题用莫队写好像有点问题,好像没有办法维护删除后的数,看大佬的题解知道要用到字典树(我还不知道什么是字典树)
看完题解后才发现这个方法好暴力啊,但是神奇的是,这个竟然没有WA也没有T,听大佬说正解是莫队+字典树复杂度是(n+m)lognsqrt(n)但是这道题也有(n^2+nm)的算法,两个算法的复杂度差距不是很大。

AC代码:

#include
#include
#include
#define LL long long
using namespace std;
const int sea=1e6+5;
int ans[sea],f[sea],a[sea],g[sea];
int n,m;
struct ask{int l,r;}q[sea];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w; 
}
int main()
{
	n=read(); m=read();
	for(int i=1;i<=sea;i++) f[i]=f[i-1]^i;//初始化f[]数组,表示前缀和 
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read();
	//好多大佬都用DP直接写了,作为蒟蒻的我看了看字典树+莫队,默默地写了DP,,, 
	for(int i=1;i<=n;i++)
	{
		int maxx=0;
		for(int j=i;j<=n;j++)//直接暴力DP即可 
		{
			int tt=f[a[i]]^f[a[j]];//a[i]的前缀和^a[j]的前缀和 
			tt^=min(a[i],a[j]);
			maxx=max(tt,maxx);
			g[j]=maxx;
		}//把整个区间两两之间异或一遍存在g[]中 
		for(int j=1;j<=m;j++)
		if(q[j].l<=i&&i<=q[j].r)//遍历一下看是不是在询问区间中 
		ans[j]=max(ans[j],g[q[j].r]);//暴力选举最大值 
	}
	//好暴力啊,这个方法,但神奇的是竟然A了!!! 
	for(int i=1;i<=m;i++)
	cout<<ans[i]<<endl;
	return 0;
}
三号板子题:

小z的袜子
引用XW大佬的话:莫队算法可以解决一类不修改、离线查询问题。
(从这道题,我看出了码代码的时候要认真啊~~~٩(º﹃º٩),死在了一个傻逼操作上,,,)
这个题就是最最最最经典的莫队算法,(好像一连写了好几个经典题目,光知道写裸题了),好像没有什么好说的,就是提醒:敲代码的时候要认真啊
关于题解公式:推荐一个大佬博客(公式推的很详细):传送门

AC代码:

#include
#define LL long long 
using namespace std;
const int sea=50010;
struct ask{int l,r,id;} q[sea];
int a[sea],belong[sea];
LL Ans,f[sea],ans1[sea],ans2[sea]; 
int n,m,L,R;
bool cmp(ask x,ask y)
{
	if(belong[x.l]==belong[y.l]) return x.r<y.r;//为什么我会写成x.r
	return belong[x.l]<belong[y.l]; 
}
void ud(int x,int d)
{
    Ans-=f[a[x]]*f[a[x]];
    f[a[x]]+=d;
    Ans+=f[a[x]]*f[a[x]];
}
LL gcd(LL x,LL y)
{
	if(y==0) return x;
	return gcd(y,x%y);
}
int main()
{
	scanf("%d%d",&n,&m);
	int block=sqrt(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]), belong[i]=i/block;
	for(int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r), q[i].id=i;	
	sort(q+1,q+m+1,cmp);
	L=1,R=0,Ans=0;
	//莫队基础操作
	for(int i=1;i<=m;i++)
	{
		if(q[i].l==q[i].r)
        {
            ans1[q[i].id]=0;
            ans2[q[i].id]=1;
            continue;
        }
        //这个算剪枝吧,也算特判吧
		while(R+1<=q[i].r) ud(R+1,1),R++;
		while(R>q[i].r) ud(R,-1),R--;
		R=q[i].r;
		while(L<q[i].l) ud(L,-1),L++;
		while(L-1>=q[i].l) ud(L-1,1),L--;
		L=q[i].l;
        //用莫队写就是比较整齐,对于强迫症的我看着人舒服
		LL aa=Ans-q[i].r+q[i].l-1;
		LL bb=(LL)(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
		//这个使用公式推出来的,可以看看上面我推荐的博客,我手推了一遍,不算难
		LL cc=gcd(aa,bb);
		aa/=cc,bb/=cc;
		ans1[q[i].id]=aa,ans2[q[i].id]=bb;
		//对于最简分数的处理 
 	}
 	for(int i=1;i<=m;i++)
 	printf("%lld/%lld\n",ans1[i],ans2[i]);
	return 0;
}
四号板子题:

Fibonacci-ish II
(发现学好英语真的好重要,,,٩(º﹃º٩))
一道正解要莫队+线段树维护,大佬说还是不难的,但作为一个我菜鸡,就推荐一下了(以后一定会补上的!!)。

带修莫队

(原来还以为莫队学完了,还好hsm大佬提醒)
带修莫队是什么呢? 莫队怎么还能修改呢???
(好烂的引入,,,,)
你会发现有一种题啊,就是让你很明显的写个莫队板子,但是就是在其中加一下什么修改,,,特别不舒服,可是在你发现要是可以修改的话比其他算法更简单,今天好像有大佬说可以Splay什么的,或是树状数组套主席树+平衡树,,,,我都记不住名字了,但我觉得还是莫队比较好写,毕竟代码短(不知道为什么就是不喜欢长代码,还超爱压行写,尽管会被大佬怼),但是根据个人喜爱去写就行,(引入是不是有点长了,,,,,)

带修莫队最最最最重要的就是时空转移,尽管这个词很花哨,但是还是要介绍一下,就是在sort(L,R)的时候加上一个指针t,就是用来记录出来时间,排序的话就按照(L,R,T)的顺序排就行,加上time以后就直接暴力修改即可。

放题:数颜色(裸题)

的的确确是一个裸题,就是赤裸裸的带修莫队,对于莫队在代码上的具体非操作及解析看代码注释
AC代码:

#include
using namespace std;
const int sea=1000010;
int f[sea*100],ans[sea],belong[sea],a[sea],now[sea],T,R,L=1,Ans,block,n,k,m,Tm;
struct hit{int l,r,id,t;}q[sea];
struct beat{int bl,New,Old;}g[sea];
//beat中的New,Old是来存新的颜色和之前的颜色
bool cmp(hit x,hit y){return belong[x.l]==belong[y.l]?(belong[x.r]==belong[y.r]?x.t<y.t:x.r<y.r):x.l<y.l;}
void ad(int x,int d)//普通莫队的修改
{
	f[x]+=d;
	if(d>0) Ans+=f[x]==1;//一个有意思的写法先判断再加
	if(d<0) Ans-=f[x]==0;
}
void dx(int x,int d)//暴力修改
{
	if(x>=L&&x<=R)
	ad(d,1),ad(a[x],-1);
	a[x]=d;
}
int main()
{
	scanf("%d%d",&n,&k);
	block=pow(n,0.666666);//这里不知道为什么网上斗都在流传n的2/3次方就可以达到最优
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),now[i]=a[i],belong[i]=i/block+1;//now[]是用来存这一块现在的颜色
	for(int i=1;i<=k;i++)
	{
		char c; int x,y;scanf(" %c %d%d",&c,&x,&y);
		if(c=='Q')     q[++m]=(hit){x,y,m,Tm};
		if(c=='R')  g[++Tm]=(beat){x,y,now[x]},now[x]=y;
	}
	sort(q+1,q+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		while(T<q[i].t)     dx(g[T+1].bl,g[T+1].New),T++;
        while(T>q[i].t)     dx(g[T].bl,g[T].Old),T--;
        //带修莫队核心思想
		while(R+1<=q[i].r)  ad(a[R+1],1),R++;
		while(R>q[i].r)     ad(a[R],-1),R--;
		while(L<q[i].l) 	ad(a[L],-1),L++;
		while(L-1>=q[i].l)  ad(a[L-1],1),L--;
		ans[q[i].id]=Ans;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);//离线莫队就代码短
	return 0;
}

树上莫队

终于终于终于,写完了这到经典题,花了我一天半天的时间啊,现在就来好好整理一下树上莫队。
树上莫队重点就是树上分块

上题:糖果公园
(第一道黑题祭)

贴心的我还是先放一下简化题解
n n n 个点构成棵树,共 m m m 种糖, q q q个询问,每一个点都只发放某种特定的糖,我们用 c i c_i ci 来表示 i 号游览点的糖果。 l l l r r r 的询问,经过每个游览点,都可以品尝到一颗对应种类的糖果。
打分:糖美味指数为 v i v_i vi,新奇指数 w i w_i wi,第 i 次品尝第 j 种糖,愉悦指数 H H H 将会为 v j ∗ w i v_j*w_i vjwi。求
∑ i = 1 n v j ∗ w i \sum_{i=1}^n v_j*w_i i=1nvjwi
有时,一些糖果点所发放的糖果种类可能会更改(也只会是 m m m 种中的一种)。(带修)
求每位游客游玩公园的愉悦指数。(离线)

题解:
(这也算是道很经典的莫队题,很值得一做)
不难看出这是个树上对于序列的操作,也就是树上分块,还可以有其他的一些做法,好像Splay也能做(然而我并不会分块以外的其他神操作,而且Splay代码太长了,,,懒得码,,),那么想到分块的话就很自然的想到了莫队,而且又没有强制在线,那么就树上莫队+带修莫队吧,(觉得自己说了一大堆废话,,,,)直接上干货:

(换元法题解)

题解=树上莫队+带修莫队
树上莫队=树上分块+莫队思想
树上分块=树上操作+分块思想
树上操作=树上dfs(欧拉)序遍历+lca倍增

AC代码:

#include
#define LL long long
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=100005;
struct hit{int l,r,t,id;}q[sea];
struct beat{int bl,New,Old;}g[sea];
struct see {int next,ver;}e[sea<<1];

int n,m,Q,tot=0,block,cnq=0,cng=0,kkk=21,dd=0,tim,blonum,top;
int belong[sea],last[sea],deep[sea],fa[17][sea],bin[20],qq[sea],num[sea];
bool vis[sea];
LL f[sea],Ans,v[sea],w[sea],c[sea],now[sea],ans[sea];
//建图 
void add(int x,int y){e[++tot].ver=y;e[tot].next=last[x];last[x]=tot;}
//带修莫队的cmp基础操作 
bool cmp(hit a,hit b)
{
	if(belong[a.l]==belong[b.l]&&belong[a.r]==belong[b.r]) return a.t<b.t;
	else if(belong[a.l]==belong[b.l]) return belong[a.r]<belong[b.r];
	else return belong[a.l]<belong[b.l];
}
// 对于树上莫队呢,首先就是欧拉序,跑过这个节点后就跑这个节点的子树,再返回去跑其他的点(具体看上面欧拉序的简介) 
// 欧拉序和DFS序的区别就是欧拉序有返回,DFS没有返回直接到叶子结点处就完了 
// 所以说这道题用DFS显然不可取,根据题意来说就是显然的欧拉序 
// LCA(倍增)的 DFS处理(对于树的深度的处理)+树上建块 
void dfs(int x)
{
	int size=top;
	for(int i=1;i<=16;i++)
	if(deep[x]>=bin[i]) fa[i][x]=fa[i-1][fa[i-1][x]];
	else break;
	for(int i=last[x];i;i=e[i].next)
	if(e[i].ver!=fa[0][x])
	{
		int y=e[i].ver;	
		deep[y]=deep[x]+1; fa[0][y]=x,dfs(y);
		//树上建块
		if(top-size>=block)
		{
			blonum++;//分块的块数 
			while(top!=size) 
			belong[qq[top--]]=blonum;//树上分块的核心 
		} 
	}
	qq[++top]=x;
}
// 求LCA
int lca(int x,int y)
{
	if(deep[x]<deep[y]) swap(x,y);
	int d=deep[x]-deep[y];
	for(int i=0;bin[i]<=d;i++)
	if(bin[i]&d) x=fa[i][x];
	for(int i=16;i>=0;--i)
	if(fa[i][x]!=fa[i][y])
	x=fa[i][x],y=fa[i][y];
	if(x==y) return x;
	return fa[0][x];
}

//带修莫队的修改  
void ad(int x)
{
	if(vis[x]) Ans-=w[num[f[x]]]*v[f[x]],num[f[x]]--;
	else num[f[x]]++,Ans+=w[num[f[x]]]*v[f[x]];
	vis[x]^=1;//当vis[]=1时vis[]=0,当vis[]=0时,vis[]=0; 
}
void change(int x,int y)
{
	if(vis[x]){ad(x);f[x]=y;ad(x);}
	else f[x]=y;
}
void so(int x,int y)
{
	while(x!=y)
	{
		if(deep[x]>deep[y]) ad(x),x=fa[0][x];
		else ad(y),y=fa[0][y];
	}
}
int main()
{
	bin[0]=1;
	for(int i=1;i<20;i++) bin[i]=bin[i-1]<<1;
	n=read(); m=read(); Q=read();
	block=pow(n,2.0/3)*0.5;
//	block=sqrt(n); 这样的话会T两个点,是卡常吗,不是很知道。 
	
	for(int i=1;i<=m;i++) v[i]=read();
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		add(x,y); add(y,x);
	}
	for(int i=1;i<=n;i++) now[i]=f[i]=read();
	
	dfs(1);
	while(top) belong[qq[top--]]=blonum;
	
	for(int i=1;i<=Q;i++)
    {
        int T=read(),x=read(),y=read();
        if(!T) g[++cng]=(beat){x,y,now[x]},now[x]=y;
        else
        {
            if(belong[q[cnq].l]>belong[q[cnq].r]) swap(q[cnq].l,q[cnq].r);
            q[++cnq]=(hit){x,y,cng,cnq};
        }
    }
	sort(q+1,q+cnq+1,cmp);
	
	for(int i=1;i<=q[1].t;i++) change(g[i].bl,g[i].New);
	so(q[1].l,q[1].r); int
	 ins=lca(q[1].l,q[1].r);
	ad(ins); ans[q[1].id]=Ans; ad(ins);  
//  先修改第一组的询问 
	for(int i=2;i<=cnq;i++)
	{
		for(int j=q[i-1].t+1;j<=q[i].t;j++) change(g[j].bl,g[j].New);
		for(int j=q[i-1].t;j>q[i].t;j--) change(g[j].bl,g[j].Old);

		so(q[i-1].l,q[i].l),so(q[i-1].r,q[i].r);
		int ins=lca(q[i].l,q[i].r);
		ad(ins); ans[q[i].id]=Ans; ad(ins);
  	}
	for(int i=1;i<=cnq;i++) printf("%lld\n",ans[i]);
	return 0;
}

回滚莫队

很有意思的一道回滚莫队裸题,但是听LDY大佬说这不就是主席树嘛,,,,但是网上有大佬说主席树不是很能写,,,算了,一个菜鸡也只能写写莫队水过了。
回滚莫队,一个很有意思的名字,其实就是在被逼无奈下的暴力优化吧,在普通的莫队是对于所选值的不断删除和添加,但如果你遇到像下面一样的维护最大值的值得时候你就会发现,,,这要是一删除不就挂了嘛,,但是要是用别的做法好像就要码很长很长的代码,就还是想用莫队,那就有个回滚莫队可供选择。

回滚莫队和普通莫队的区别就是,回滚莫队的L值是这个块中的最右端R+1(不能完全说是下一块的头,因为有可能是最后一块),右端点一开始在块尾(因为左右端点同块的我们都暴力解决了,剩下的右端点肯定至少在下一块),然后正常向右,当右端点走到询问区间右端点的时候,把左端点造成的影响还原,然后又滚回到块尾,因此左右端点都没有删除,只偶遇添加。

上题:历史研究
一道我调了很久才发现是编译环境不同的问题,,,(调到快疯了,还是LDY这位大神指点迷津,感谢大神!
就是那个cmp函数,(我自己A过了我都不知道)害我白白调了一节课。。。
莫队算法_第2张图片
AC 代码:

#include
#define LL long long
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=1e5 + 10;
struct hit {int l,r,id;}q[sea];
int n,m,tot,block,num;
LL ans[sea],Ans;
int belong[sea],a[sea],b[sea],f[sea];
//bool cmp(hit x,hit y){return belong[x.l]^belong[y.l]?belong[x.l]y.r;}
//在BZOJ上这个优化过不去,但在洛谷上能过去
bool cmp(hit x,hit y){return belong[x.l]==belong[y.l]?x.r<y.r:x.l<y.l;}
LL blf(int l,int r))//暴力查询
{
	static int time[sea];//静态局部变量
	LL s=0;
	for(int i=l;i<=r;i++) time[a[i]]=0;
	for(int i=l;i<=r;i++) time[a[i]]++,s=max(s,1ll*time[a[i]]*b[a[i]]);
	return s;
}
void add(int x){f[a[x]]++,Ans=max(Ans,1ll*b[a[x]]*f[a[x]]);}
void del(int x){f[a[x]]--;}
int get(int i,int id
{
	int RR=min(n,block*id),L=RR+1,R=L-1; Ans=0;
	memset(f,0,sizeof(f));
	for(;id==belong[q[i].l];i++)
	{
		//在同一块中直接暴力解决
		if(belong[q[i].l]==belong[q[i].r]){ans[q[i].id]=blf(q[i].l,q[i].r);continue;}
//      不在同一块中的处理
		while(R<q[i].r) add(++R);
		LL ins=Ans;
		while(L>q[i].l) add(--L);
		ans[q[i].id]=Ans;
		while(L<RR+1)   del(L++);//每次询问完之后重新统计答案
		Ans=ins;
	}
	return i;
}
int main()
{
	n=read(); m=read(); block=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		a[i]=b[i]=read();
		belong[i]=(i-1)/block+1;
		num=max(num,belong[i]);
	}
	sort(b+1,b+n+1);
	int tot=unique(b+1,b+n+1)-b-1;//去重
	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n+1,a[i])-b;//求有限制的最大值
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+m+1,cmp);
	for(int i=1,id=1;id<=num;id++) i=get(i,id);//枚举所有块
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

总结:
整个莫队算法的学习还算顺吧,其实我觉得莫队就是分块的优化,对询问进行排序,解决一切离线询问的题目,从而降低时间复杂度。

既然华山一路,我就不终不会回头。——blng

你可能感兴趣的:(学习记录,莫队算法,高级数据结构)