树上莫队小技巧

前言

联考有一道树上莫队一眼题,但是我还没学过树上莫队啊!!!

于是开始口胡,这个东西好像是说这个东西把树拍成欧拉序,端点一移动,做完了!开始写,一下子过大样例,没有细节!

然后在网上一看树上莫队的博客:大家怎么都求了 LCA?为什么要分讨有祖先后代关系的情况?坏了,一定是我做法假了!!!

然后往 SPOJ Count on a tree 2 一交,怎么 T 了?哦,原来是对询问 sort 后才预处理了分块数组,小问题。再一交,欸,过了!

正经的

其实欧拉序树上莫队有一种细节非常少的写法,我们为什么要利用欧拉环游序呢?因为它满足一个非常优秀的性质:相邻两个数在原树上一定相邻。

而莫队让我们做什么?区间左端点右端点变化一。树上莫队能做什么?路径两端点变到相邻的节点。

所以说我们想做路径询问,维护两个指针 \(u,v\) 表示路径两端点,再用一种数据结构存储路径信息,我们只需要保证任意时刻这个数据结构中存储的正好是 \(u,v\) 之间路径的信息就行了。

所以我们树上莫队时对于询问 \(x,y\) 在欧拉序上随便找两个值为 \(x/y\) 的位置 \(l,r\),就把路径询问当成普通莫队中的一个区间。

移动指针时,每次我们只会把 \(u,v\) 移动到相邻节点,那么路径变化是 \(O(1)\) 的:存储一个表示每个点是否在路径上的 bool 数组,每次移动的时候判一下移动到的节点有没有访问来决定是插入还是删除就可以了。

#include <cmath>
#include <cstdio>
#include <bitset>
#include <algorithm>
using namespace std;
int read(){/*...*/}
const int N=100003,M=200003;
int hd[N],ver[M],nxt[M],tot;
void add(int u,int v){nxt[++tot]=hd[u];hd[u]=tot;ver[tot]=v;}
int n,q,bl,cnt;
int pos[N],num,id[M],a[N],bc[N];
void dfs(int u,int fa){
	id[pos[u]=++num]=u;
	for(int i=hd[u];i;i=nxt[i]){
		int v=ver[i];
		if(v==fa) continue;
		dfs(v,u);
		id[pos[u]=++num]=u;
	}
}
bool vis[N];
int res[M];
void mov(int u,int v){
	if(vis[v]){if(!--bc[a[u]]) --cnt;vis[u]=0;}
	else{if(!bc[a[v]]++) ++cnt;vis[v]=1;}
}
int bel[M],buc[M],rk;
struct qry{
	int l,r,x;
	friend bool operator<(const qry x,const qry y){
		int tx=bel[x.l],ty=bel[y.l];
		if(tx!=ty) return tx<ty;
		if(tx&1) return x.r>y.r;
		else return x.r<y.r;
	}
}s[M];
int main(){
	n=read();q=read();
	for(int i=1;i<=n;++i) buc[++rk]=a[i]=read();
	sort(buc+1,buc+rk+1);
	rk=unique(buc+1,buc+rk+1)-buc-1;
	for(int i=1;i<=n;++i) a[i]=lower_bound(buc+1,buc+rk+1,a[i])-buc;
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		add(u,v);add(v,u);
	}
	dfs(1,0);
	for(int i=1;i<=q;++i){
		s[i].l=pos[read()];s[i].r=pos[read()];s[i].x=i;
		if(s[i].l>s[i].r) swap(s[i].l,s[i].r);
	}
	bl=ceil((double)num/sqrt(q));
	for(int i=1;i<=num;++i) bel[i]=(i-1)/bl+1;
	sort(s+1,s+q+1);
	int cl=1,cr=1;
	++bc[a[id[1]]];cnt=1;vis[id[1]]=1;
	for(int i=1;i<=q;++i){
		while(cr<s[i].r) mov(id[cr],id[cr+1]),++cr;
		while(cl>s[i].l) mov(id[cl],id[cl-1]),--cl;
		while(cr>s[i].r) mov(id[cr],id[cr-1]),--cr;
		while(cl<s[i].l) mov(id[cl],id[cl+1]),++cl;
		res[s[i].x]=cnt;
	}
	for(int i=1;i<=q;++i) printf("%d\n",res[i]);
	return 0;
}
posted @ 2023-10-14 09:53  yyyyxh  阅读(40)  评论(0编辑  收藏  举报