1. 程式人生 > 實用技巧 >跟光磊學Java開發-JDBC程式設計實戰

跟光磊學Java開發-JDBC程式設計實戰

推薦幾篇好的部落格

分塊入門

莫隊演算法——從入門到黑題

莫隊演算法初探

二維莫隊解題報告

回滾莫隊拓展

以及這個題單

普通莫隊

莫隊演算法是一種基於分塊思想的離線演算法

可以用來維護區間的答案和區間的資料結構

首先我們把長度為 \(n\) 的序列劃分為 \(\sqrt{n}\) 個長度為 \(\sqrt{n}\) 的塊並標號

然後把所有的詢問離線下來,按照左端點所在的塊的標號排序,如果標號相同,則按照右端點從小到大排序

詢問的時候維護一個區間最左端的指標和一個區間最右端的指標

每次處理一個新的詢問時,就分別移動左指標和右指標,使它們分別與新的詢問的左右端點重合並記錄答案

最後把存到數組裡的答案輸出即可

一般來說,\(n\)\(m\) 可以看做是相等的,所以塊長取 \(\sqrt{n}\) 最優

對於右端點來說,每一個塊內的右端點是單調的,最多移動 \(n\) 的長度,一共有 \(\sqrt{n}\) 個塊,所以複雜度為 \(n\sqrt{n}\)

對於左端點來說,每次最多移動一個塊長,一共有 \(n\) 個左端點,所以複雜度為 \(n\sqrt{n}\)

再加上排序的 \(nlogn\) ,總的時間複雜度就是 \(n\sqrt{n}\)

但是當 \(n\)\(m\) 不相等的時候,塊長取 \(\frac{n}{\sqrt{m}}\) 是最優的,此時時間複雜度為 \(n\sqrt{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\n",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\n",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\n",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\n",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\n",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) \neq 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\n",ans[i]);
	}
	return 0;
}