莫队学习笔记

推荐几篇好的博客

分块入门

莫队算法——从入门到黑题

莫队算法初探

二维莫队解题报告

回滚莫队拓展

以及这个题单

普通莫队

莫队算法是一种基于分块思想的离线算法

可以用来维护区间的答案和区间的数据结构

首先我们把长度为 (n) 的序列划分为 (sqrt{n}) 个长度为 (sqrt{n}) 的块并标号

然后把所有的询问离线下来,按照左端点所在的块的标号排序,如果标号相同,则按照右端点从小到大排序

询问的时候维护一个区间最左端的指针和一个区间最右端的指针

每次处理一个新的询问时,就分别移动左指针和右指针,使它们分别与新的询问的左右端点重合并记录答案

最后把存到数组里的答案输出即可

一般来说,(n)(m) 可以看做是相等的,所以块长取 (sqrt{n}) 最优

对于右端点来说,每一个块内的右端点是单调的,最多移动 (n) 的长度,一共有 (sqrt{n}) 个块,所以复杂度为 (nsqrt{n})

对于左端点来说,每次最多移动一个块长,一共有 (n) 个左端点,所以复杂度为 (nsqrt{n})

再加上排序的 (nlogn) ,总的时间复杂度就是 (nsqrt{n})

但是当 (n)(m) 不相等的时候,块长取 (frac{n}{sqrt{m}}) 是最优的,此时时间复杂度为 (nsqrt{m})

使用莫队算法的前提是没有强制在线,并且每次移动左指针和右指针改变的贡献能够快速计算

莫队算法在排序的时候还有一个奇偶性优化

如果左端点所属的联通块标号为奇数,按照右端点从小到大排序

否则按照右端点从大到小排序

这样排序大约要快 (30\%) 左右

代码

洛谷P2709 小B的询问

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
int shuyu[maxn],a[maxn],n,m,k,cnt[maxn],num,blo,ans[maxn];
struct asd{
	int jla,jlb,id;
}b[maxn];
bool cmp(asd aa,asd bb){
	if(shuyu[aa.jla]==shuyu[bb.jla]) return shuyu[aa.jla]&1?aa.jlb<bb.jlb:aa.jlb>bb.jlb;
	return aa.jla<bb.jla;
}
void xg(rg int now,rg int jud){
	num-=cnt[now]*cnt[now];
	cnt[now]+=jud;
	num+=cnt[now]*cnt[now];
}
int main(){
	n=read(),m=read(),k=read();
	blo=sqrt(n);
	for(rg int i=1;i<=n;i++){
		a[i]=read();
		shuyu[i]=(i-1)/blo+1;
	}
	for(rg int i=1;i<=m;i++){
		b[i].jla=read(),b[i].jlb=read(),b[i].id=i;
	}
	std::sort(b+1,b+1+m,cmp);
	rg int l=1,r=0;
	for(rg int i=1;i<=m;i++){
		while(r<b[i].jlb) xg(a[++r],1);
		while(l>b[i].jla) xg(a[--l],1);
		while(r>b[i].jlb) xg(a[r--],-1);
		while(l<b[i].jla) xg(a[l++],-1);
		ans[b[i].id]=num;
	}
	for(rg int i=1;i<=m;i++) printf("%d
",ans[i]);
	return 0;
}

二维莫队

莫队也可以处理二维平面上的问题

但是排序的方式要改变一下

第一关键字:左上角的点的横坐标

第二关键字:左上角的点的纵坐标

第三关键字:右下角的点的横坐标

第四关键字:右下角的点的纵坐标

复杂度 (n^2m^{frac{3}{4}})

代码

#2639. 矩形计算

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=205,maxm=1e6+5;
int n,m,q,a[maxn][maxn],shuyu[maxn],blo,ans[maxm],nans,sta[maxm],top,cnt[maxm];
struct asd{
	int ax,ay,bx,by,id;
	asd(){}
	asd(int aa,int bb,int cc,int dd,int ee){
		ax=aa,ay=bb,bx=cc,by=dd,id=ee;
	}
}b[maxm];
bool cmp(asd aa,asd bb){
	if(shuyu[aa.ax]==shuyu[bb.ax]){
		if(shuyu[aa.ay]==shuyu[bb.ay]){
			if(shuyu[aa.bx]==shuyu[bb.bx]) return shuyu[aa.by]<shuyu[bb.by];
			else return shuyu[aa.bx]<shuyu[bb.bx];
		} else {
			return shuyu[aa.ay]<shuyu[bb.ay];
		}
	} else {
		return shuyu[aa.ax]<shuyu[bb.ax];
	}
}
void xgh(int aa,int bb,int hs,int op){
	for(rg int i=aa;i<=bb;i++){
		nans-=cnt[a[hs][i]]*cnt[a[hs][i]];
		cnt[a[hs][i]]+=op;
		nans+=cnt[a[hs][i]]*cnt[a[hs][i]];
	}
}
void xgl(int aa,int bb,int ls,int op){
	for(rg int i=aa;i<=bb;i++){
		nans-=cnt[a[i][ls]]*cnt[a[i][ls]];
		cnt[a[i][ls]]+=op;
		nans+=cnt[a[i][ls]]*cnt[a[i][ls]];
	}
}
int main(){
	n=read(),m=read();
	blo=sqrt(std::max(n,m))/2*3;
	for(rg int i=1;i<=n;i++){
		for(rg int j=1;j<=m;j++){
			a[i][j]=read();
			sta[++top]=a[i][j];
		}
	}
	std::sort(sta+1,sta+1+top);
	rg int haha=std::unique(sta+1,sta+1+top)-sta-1;
	for(rg int i=1;i<=n;i++){
		for(rg int j=1;j<=m;j++){
			a[i][j]=std::lower_bound(sta+1,sta+1+haha,a[i][j])-sta;
		}
	}
	q=read();
	rg int aa,bb,cc,dd;
	for(rg int i=1;i<=q;i++){
		aa=read(),bb=read(),cc=read(),dd=read();
		if(aa>cc) std::swap(aa,cc);
		if(bb>dd) std::swap(bb,dd);
		b[i]=asd(aa,bb,cc,dd,i);
	}
	for(rg int i=1;i<=std::max(n,m);i++){
		shuyu[i]=(i-1)/blo+1;
	}
	std::sort(b+1,b+1+q,cmp);
	rg int nx=1,ny=1,mx=1,my=1;
	nans=1;
	cnt[a[1][1]]=1;
	for(rg int i=1;i<=q;i++){
		while(mx<b[i].bx){
			mx++;
			xgh(ny,my,mx,1);
		}
		while(my<b[i].by){
			my++;
			xgl(nx,mx,my,1);
		}
		while(nx>b[i].ax){
			nx--;
			xgh(ny,my,nx,1);
		}
		while(ny>b[i].ay){
			ny--;
			xgl(nx,mx,ny,1);
		}
		while(mx>b[i].bx){
			xgh(ny,my,mx,-1);
			mx--;
		}
		while(my>b[i].by){
			xgl(nx,mx,my,-1);
			my--;
		}
		while(nx<b[i].ax){
			xgh(ny,my,nx,-1);
			nx++;
		}
		while(ny<b[i].ay){
			xgl(nx,mx,ny,-1);
			ny++;
		}
		ans[b[i].id]=nans;
	}
	for(rg int i=1;i<=q;i++){
		printf("%d
",ans[i]);
	}
	return 0;
}

带修莫队

普通莫队加上了修改操作

那么排序的时候也要把修改的时间作为一个关键字排序

第一关键字:左端点属于的块

第二关键字:右端点属于的块

第三关键字:到当前查询的时候已经进行了多少次修改操作

当块长为 (n^{frac{2}{3}}) 时,复杂度为 (n^frac{5}{3})

代码

CF940F Machine Learning

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5,mod=1e6+3;
int n,a[maxn],q,shuyu[maxn],cnt[maxn],mp[maxn],blo,ans[maxn],nans,sta[maxn],tp,cnt1,cnt2,jl[maxn];
struct jie{
	int l,r,id,tim;
	jie(){}
	jie(rg int aa,rg int bb,rg int cc,rg int dd){
		l=aa,r=bb,id=cc,tim=dd;
	}
}b[maxn];
bool cmp(jie aa,jie bb){
	if(shuyu[aa.l]==shuyu[bb.l] && shuyu[aa.r]==shuyu[bb.r]){
		return shuyu[aa.r]&1?aa.tim<bb.tim:aa.tim>bb.tim;
	} else if(shuyu[aa.l]==shuyu[bb.l]){
		return shuyu[aa.l]&1?aa.r<bb.r:aa.r>bb.r;
	} else {
		return shuyu[aa.l]<shuyu[bb.l];
	}
}
void ad(rg int val){
	mp[cnt[val]]--;
	cnt[val]++;
	mp[cnt[val]]++;
}
void sc(rg int val){
	mp[cnt[val]]--;
	cnt[val]--;
	mp[cnt[val]]++;
}
struct asd{
	int wz,bef,lat;
	asd(){}
	asd(rg int aa,rg int bb,rg int cc){
		wz=aa,bef=bb,lat=cc;
	}
}c[maxn];
int main(){
	n=read(),q=read();
	blo=pow(n,2.0/3.0);
	for(rg int i=1;i<=n;i++){
		jl[i]=a[i]=read();
		sta[++tp]=a[i];
		shuyu[i]=(i-1)/blo+1;
	}
	rg int aa,bb,cc;
	for(rg int i=1;i<=q;i++){
		aa=read(),bb=read(),cc=read();
		if(aa==1){
			cnt1++;
			b[cnt1]=jie(bb,cc,cnt1,cnt2);
		} else {
			cnt2++;
			c[cnt2]=asd(bb,jl[bb],cc);
			sta[++tp]=cc;
			jl[bb]=cc;
		}
	}
	std::sort(sta+1,sta+1+tp);
	tp=std::unique(sta+1,sta+1+tp)-sta-1;
	for(rg int i=1;i<=n;i++) a[i]=std::lower_bound(sta+1,sta+1+tp,a[i])-sta;
	for(rg int i=1;i<=cnt2;i++){
		c[i].bef=std::lower_bound(sta+1,sta+1+tp,c[i].bef)-sta;
		c[i].lat=std::lower_bound(sta+1,sta+1+tp,c[i].lat)-sta;
	}
	std::sort(b+1,b+1+cnt1,cmp);
	rg int nl=1,nr=0,ntim=0;
	for(rg int i=1;i<=cnt1;i++){
		while(ntim<b[i].tim){
			ntim++;
			if(c[ntim].wz>=nl && c[ntim].wz<=nr){
				sc(c[ntim].bef);
				ad(c[ntim].lat);
			}
			a[c[ntim].wz]=c[ntim].lat;
		}
		while(ntim>b[i].tim){
			if(c[ntim].wz>=nl && c[ntim].wz<=nr){
				ad(c[ntim].bef);
				sc(c[ntim].lat);
			}
			a[c[ntim].wz]=c[ntim].bef;
			ntim--;
		}
		while(nr<b[i].r){
			nr++;
			ad(a[nr]);
		}
		while(nl>b[i].l){
			nl--;
			ad(a[nl]);
		}
		while(nr>b[i].r){
			sc(a[nr]);
			nr--;
		}
		while(nl<b[i].l){
			sc(a[nl]);
			nl++;
		}
		nans=1;
		while(mp[nans]) nans++;
		ans[b[i].id]=nans;
	}
	for(rg int i=1;i<=cnt1;i++) printf("%d
",ans[i]);
	return 0;
}

回滚莫队

有些情况下,添加值很好做,但是删除值不好做

还有些情况下,删除值很好做,但是添加值不好做

此时普通的莫队无法做到移动左右指针的时候单次 (O(1)) 更新答案

就要用到另一种莫队:回滚莫队

回滚莫队又分为两种:只加不减的和只减不加的

只加不减

AT1219 歴史の研究

这道题要维护最大值

区间伸长的时候很好说,直接和当前的答案取个 (max) 即可

但是区间收缩的时候我们却无法快速地找到次大值,即使我们记录了次大值,有可能下一次又把次大值删除

所以我们要让莫队只能加入贡献,不能删除贡献

首先,我们把询问按照莫队的顺序排序

注意排序时不能使用奇偶性优化,而要按照右端点递增来排

排完序后,询问被分成了 (sqrt{n})

我们把每一段拿出来单独处理

(r[i]) 为第 (i) 段的右端点的下标,(l[i])为第 (i) 段的左端点的下标

一开始,我们把左指针设为 (r[i]+1)

把右指针设为 (r[i])

因为在每一段内右端点都是单调递增的,所以右端点一定只有加入的贡献

对于左端点,为了强制只能加入值

我们要在每次操作使用完成后把它回复成 (r[i]+1) 的状态 ,同时把之前左端点做的贡献清除

每一段统计完答案后,我们还要把右端点的贡献清清除

对于长度小于 (sqrt{n}) 的块,暴力统计即可

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5;
int n,a[maxn],q,shuyu[maxn],cnt[maxn],blo,sta[maxn],tp,lmax[maxn],rmax[maxn],cnt2[maxn];
long long ans[maxn],nans;
struct jie{
	int l,r,id;
	jie(){}
	jie(rg int aa,rg int bb,rg int cc){
		l=aa,r=bb,id=cc;
	}
}b[maxn];
bool cmp(jie aa,jie bb){
	if(shuyu[aa.l]==shuyu[bb.l]){
		return aa.r<bb.r;
	} else {
		return shuyu[aa.l]<shuyu[bb.l];
	}
}
void ad(rg int val){
	cnt[val]++;
	nans=std::max(nans,1LL*sta[val]*cnt[val]);
}
long long solve(rg int l,rg int r){
	rg long long mans=0;
	for(rg int i=l;i<=r;i++) cnt2[a[i]]++;
	for(rg int i=l;i<=r;i++){
		mans=std::max(mans,1LL*sta[a[i]]*cnt2[a[i]]);
	}
	for(rg int i=l;i<=r;i++) cnt2[a[i]]--;
	return mans;
}
int main(){
	memset(lmax,0x3f,sizeof(lmax));
	n=read(),q=read();
	blo=sqrt(n);
	for(rg int i=1;i<=n;i++){
		a[i]=read();
		sta[++tp]=a[i];
		shuyu[i]=(i-1)/blo+1;
	}
	std::sort(sta+1,sta+1+tp);
	tp=std::unique(sta+1,sta+1+tp)-sta-1;
	for(rg int i=1;i<=n;i++) a[i]=std::lower_bound(sta+1,sta+1+tp,a[i])-sta;
	for(rg int i=1;i<=n;i++){
		rmax[shuyu[i]]=std::max(rmax[shuyu[i]],i);
		lmax[shuyu[i]]=std::min(lmax[shuyu[i]],i);
	}
	for(rg int i=1;i<=q;i++){
		b[i].l=read(),b[i].r=read(),b[i].id=i;
	}
	std::sort(b+1,b+1+q,cmp);
	rg int nl=1,nr=0,now=1;
	rg long long tmp;
	for(rg int i=1;i<=shuyu[n];i++){
		memset(cnt,0,sizeof(cnt));
		nr=rmax[i];
		nans=0;
		while(shuyu[b[now].l]==i){
			nl=rmax[i]+1;
			if(b[now].r-b[now].l<=blo){
				ans[b[now].id]=solve(b[now].l,b[now].r);
				now++;
				continue;
			}
			while(nr<b[now].r) ad(a[++nr]);
			tmp=nans;
			while(nl>b[now].l) ad(a[--nl]);
			ans[b[now].id]=nans;
			nans=tmp;
			while(nl<=rmax[i]) cnt[a[nl++]]--;
			now++;
		}
	}
	for(rg int i=1;i<=q;i++) printf("%lld
",ans[i]);
	return 0;
}

只减不加

P4137 Rmq Problem / mex

和只加不减的莫队同理

只要把右端点改成单调递减

把一开始的右指针置为 (n),左指针置为 (l[i]) 即可

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=2e5+5;
int n,a[maxn],q,shuyu[maxn],cnt[maxn],blo,lmax[maxn],rmax[maxn],cnt2[maxn],ans[maxn],nans,cnt3[maxn];
struct jie{
	int l,r,id;
	jie(){}
	jie(rg int aa,rg int bb,rg int cc){
		l=aa,r=bb,id=cc;
	}
}b[maxn];
bool cmp(jie aa,jie bb){
	if(shuyu[aa.l]==shuyu[bb.l]){
		return aa.r>bb.r;
	} else {
		return shuyu[aa.l]<shuyu[bb.l];
	}
}
void sc(rg int val){
	cnt[val]--;
	if(cnt[val]==0) nans=std::min(nans,val);
}
int solve(rg int l,rg int r){
	rg int mans=0;
	for(rg int i=l;i<=r;i++){
		cnt2[a[i]]++;
	}
	while(cnt2[mans]) mans++;
	for(rg int i=l;i<=r;i++){
		cnt2[a[i]]--;
	}
	return mans;
}
int main(){
	memset(lmax,0x3f,sizeof(lmax));
	n=read(),q=read();
	blo=sqrt(n);
	for(rg int i=1;i<=n;i++){
		a[i]=read();
		shuyu[i]=(i-1)/blo+1;
	}
	for(rg int i=1;i<=n;i++){
		rmax[shuyu[i]]=std::max(rmax[shuyu[i]],i);
		lmax[shuyu[i]]=std::min(lmax[shuyu[i]],i);
	}
	for(rg int i=1;i<=q;i++){
		b[i].l=read(),b[i].r=read(),b[i].id=i;
	}
	std::sort(b+1,b+1+q,cmp);
	rg int nl=1,nr=0,now=1,tmp,cs;
	for(rg int i=1;i<=shuyu[n];i++){
		nans=0;
		nr=n;
		for(rg int j=lmax[i];j<=nr;j++) cnt[a[j]]++;
		while(cnt[nans]) nans++;
		while(shuyu[b[now].l]==i){
			if(b[now].r-b[now].l<=blo){
				ans[b[now].id]=solve(b[now].l,b[now].r);
				now++;
				continue;
			}
			nl=lmax[i];
			while(nr>b[now].r){
				sc(a[nr]);
				nr--;
			}
			tmp=nans;
			for(rg int j=nl;j<b[now].l;j++){
				cnt3[a[j]]=cnt[a[j]];
			}
			cs=nl;
			while(nl<b[now].l){
				sc(a[nl]);
				nl++;
			}
			for(rg int j=cs;j<b[now].l;j++){
				cnt[a[j]]=cnt3[a[j]];
			}
			ans[b[now].id]=nans;
			nans=tmp;
			now++;
		}
		for(rg int j=lmax[i];j<=n;j++) cnt[a[j]]=0;
	}
	for(rg int i=1;i<=q;i++) printf("%d
",ans[i]);
	return 0;
}

回滚莫队的复杂度和普通莫队是相同的

树上莫队

首先我们要把树变成一个序列

普通的 (dfn) 序可以处理子树上的问题

但是处理路径问题就要用到欧拉序

欧拉序的求法很简单,在刚 (dfs) 到一个点时加入序列,最后退出时也加入一遍

(eul1[i]) 表示访问到 (i) 时加入欧拉序的时间,(eul2[i]) 表示回溯经过 (i) 时加入欧拉序的时间

(eul1[x]<eul1[y])

(lca(x,y) = x),这时 (x,y)在一条链上,那么(eul1[x])(eul1[y]) 这段区间中,有的点出现了两次,有的点没有出现过,这些点都是对答案没有贡献的,我们只需要统计出现过 (1) 次的点就好

(lca(x,y) eq x),此时 (x,y) 位于不同的子树内,我们只需要按照上面的方法统计 (eul2[x])(eul1[y]) 这段区间内的点,但是我们发现这里面的点不包括 (lca),所以要把 (lca) 加上

代码

SP10707 COT2 - Count on a tree II

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5;
int h[maxn],tot=1,n,q,blo,a[maxn],sta[maxn],tp2,eul1[maxn],eul2[maxn],dfn[maxn],dfnc;
bool vis[maxn];
struct asd{
	int to,nxt;
}b[maxn];
void ad(rg int aa,rg int bb){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	h[aa]=tot++;
}
int siz[maxn],dep[maxn],son[maxn],fa[maxn],rk[maxn],tp[maxn],nans,ans[maxn],cnt[maxn];
void dfs1(rg int now,rg int lat){
	dep[now]=dep[lat]+1;
	eul1[now]=++dfnc;
	rk[dfnc]=now;
	siz[now]=1;
	fa[now]=lat;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		dfs1(u,now);
		siz[now]+=siz[u];
		if(siz[u]>siz[son[now]]) son[now]=u;
	}
	eul2[now]=++dfnc;
	rk[dfnc]=now;
}
void dfs2(rg int now,rg int top){
	tp[now]=top;
	if(son[now]) dfs2(son[now],top);
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==fa[now] || u==son[now]) continue;
		dfs2(u,u);
	}
}
int getlca(rg int xx,rg int yy){
	while(tp[xx]!=tp[yy]){
		if(dep[tp[xx]]<dep[tp[yy]]) std::swap(xx,yy);
		xx=fa[tp[xx]];
	}
	if(dep[xx]<dep[yy]) return xx;
	return yy;
}
int shuyu[maxn];
struct jie{
	int l,r,id,anc;
	jie(){}
	jie(rg int aa,rg int bb,rg int cc,rg int dd){
		l=aa,r=bb,id=cc,anc=dd;
	}
}c[maxn];
bool cmp(rg jie aa,rg jie bb){
	if(shuyu[aa.l]==shuyu[bb.l]){
		return shuyu[aa.l]&1?aa.r<bb.r:aa.r>bb.r;
	} else {
		return shuyu[aa.l]<shuyu[bb.l];
	}
}
void xg(rg int now){
	rg int val=vis[now]?-1:1;
	if(cnt[a[now]]==0) nans++;
	cnt[a[now]]+=val;
	if(cnt[a[now]]==0) nans--;
	vis[now]^=1;
}
int main(){
	memset(h,-1,sizeof(h));
	n=read(),q=read();
	blo=sqrt(n+n);
	for(rg int i=1;i<=n;i++){
		a[i]=read();
		sta[++tp2]=a[i];
	}
	for(rg int i=1;i<=n+n;i++){
		shuyu[i]=(i-1)/blo+1;
	}
	std::sort(sta+1,sta+1+tp2);
	tp2=std::unique(sta+1,sta+1+tp2)-sta-1;
	for(rg int i=1;i<=n;i++) a[i]=std::lower_bound(sta+1,sta+1+tp2,a[i])-sta;
	rg int aa,bb,cc;
	for(rg int i=1;i<n;i++){
		aa=read(),bb=read();
		ad(aa,bb);
		ad(bb,aa);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(rg int i=1;i<=q;i++){
		aa=read(),bb=read();
		if(eul1[aa]>eul1[bb]) std::swap(aa,bb);
		cc=getlca(aa,bb);
		if(cc==aa){
			c[i]=jie(eul1[aa],eul1[bb],i,0);
		} else {
			c[i]=jie(eul2[aa],eul1[bb],i,cc);
		}
	}
	std::sort(c+1,c+1+q,cmp);
	rg int nl=1,nr=0;
	for(rg int i=1;i<=q;i++){
		while(nr<c[i].r) xg(rk[++nr]);
		while(nl>c[i].l) xg(rk[--nl]);
		while(nr>c[i].r) xg(rk[nr--]);
		while(nl<c[i].l) xg(rk[nl++]);
		if(c[i].anc) xg(c[i].anc);
		ans[c[i].id]=nans;
		if(c[i].anc) xg(c[i].anc);
	}
	for(rg int i=1;i<=q;i++){
		printf("%d
",ans[i]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/liuchanglc/p/14265687.html