题解:luogu P3248 ([HNOI2016] 树)

1. Solution

写一个和其他题解都不一样的神秘赛时思路。
首先我们有一个显而易见的暴力,就是根据每一次操作把树建出来,这棵树的点数是 \(O(nm)\) 的,当 \(n=10^5,m=10^5\) 的时候,显然是无法接受的,我们考虑优化。
我们发现有效点的数量实际上并不多,原树上的 \(n\) 个点姑且都算作有效点,每一次粘贴操作的 \(to\),每一次查询操作的 \(fr\)\(to\),实际上只有 \(4\times 10^5\) 个。
诶,像这种原树节点很多,但是有效点尚可接受的范围的题,我们不难想到可以利用虚树来写。
具体的,我们将所有修改操作和询问操作离线,记录所有有效点,然后执行修改操作,不难算出某一次修改操作对应的编号区间,然后我们把这个编号区间内的有效点全部拿出来建立一棵虚树,然后把这棵虚树连到对应的点上。 由于原树上点的编号很大,所以考虑使用一个 map 将原树上点的编号映射到新树上。
具体建虚树的时候,我们先通过主席树算出这些有效点在模板树上对应的点,然后用模板树上点的编号建立虚树,最后通过主席树把模板树上的点再对应回原树即可。
最后查询的时候,在虚树上查询两点距离,很简单,不过多赘述。

2. Code

/*by ChenMuJiu*/
/*略去缺省源与快读快写*/
const int N=1e5+5,M=2e6+5;
int n,m,q,tot,cnt_dfn;
int dis[N],siz[N],rt[N],dfn[N],L[N],R[N],a[N<<1];
ll idx[N*3];
map<ll,int>mp;
pii opt[N],query[N];
vector<int>e[N];
struct Segment_tree{
	int num;
	int cnt[M],ls[M],rs[M];
	#define mid (l+r>>1)
	int New(){
		int p=++num;
		cnt[p]=0,ls[p]=0,rs[p]=0;
		return p;
	}
	int copy(int p){
		int q=New();
		cnt[q]=cnt[p],ls[q]=ls[p],rs[q]=rs[p];
		return q;
	}
	void pushup(int p){
		cnt[p]=cnt[ls[p]]+cnt[rs[p]];
	}
	int build(int l,int r){
		int p=New();
		if(l==r)return p;
		ls[p]=build(l,mid),rs[p]=build(mid+1,r);
		return p;
	}
	int change(int p,int l,int r,int x){
		int q=copy(p);
		if(l==r){
			cnt[q]++;
			return q;
		}
		if(mid>=x)ls[q]=change(ls[p],l,mid,x);
		else rs[q]=change(rs[p],mid+1,r,x);
		pushup(q);
		return q;
	}
	int querykth(int p,int q,int l,int r,int k){
		if(l==r)return l;
		if(cnt[ls[q]]-cnt[ls[p]]>=k)return querykth(ls[p],ls[q],l,mid,k);
		k-=cnt[ls[q]]-cnt[ls[p]];
		return querykth(rs[p],rs[q],mid+1,r,k);
	}
	int querysum(int p,int q,int l,int r,int L,int R){
		if(L<=l&&r<=R)return cnt[q]-cnt[p];
		int sum=0;
		if(mid>=L)sum+=querysum(ls[p],ls[q],l,mid,L,R);
		if(mid<R)sum+=querysum(rs[p],rs[q],mid+1,r,L,R);
		return sum;
	}
	int queryrk(int p,int q,int x){
		if(x==1)return 1;
		return querysum(p,q,1,n,1,x-1)+1;
	}
	#undef mid
}Set;
namespace CalLca{
#define Min(x,y) (dep[x]<=dep[y]?x:y)
int cnt_dfn;
int dep[N],dfn[N<<1],L[N];
int st[20][N<<1];
void dfs(int u,int fa){
	dfn[++cnt_dfn]=u;
	L[u]=cnt_dfn;
	for(int v:e[u]){
		if(v==fa)continue;
		dep[v]=dep[u]+1;
		dfs(v,u);
		dfn[++cnt_dfn]=u;
	}
}
void init(){
	dfs(1,0);
	for(int i=1;i<=cnt_dfn;i++)
		st[0][i]=dfn[i];
	for(int j=1;j<20;j++)
		for(int i=1;i+(1<<j)-1<=cnt_dfn;i++)
			st[j][i]=Min(st[j-1][i],st[j-1][i+(1<<j-1)]);		
}
int LCA(int u,int v){
	if(u==v)return u;
	if(L[u]<L[v])swap(u,v);
	int j=__lg(L[u]-L[v]+1);
	return Min(st[j][L[v]],st[j][L[u]-(1<<j)+1]);
}
#undef Min
}
namespace Tree{
const int M=7e5+5;
int num;
int fa[M],dep[M],top[M],siz[M],son[M];
ll dis[M];
struct Graph{
	struct Edge{
		int v,w,nxt;
	}e[M];
	int cnt_edge;
	int head[M];
	void AddEdge(int u,int v,int w){
		e[++cnt_edge]={v,w,head[u]};
		head[u]=cnt_edge;
	}
}G;
void dfs(int u){
	siz[u]=1;
	for(int i=G.head[u];i;i=G.e[i].nxt){
		int v=G.e[i].v,w=G.e[i].w;
		fa[v]=u;
		dis[v]=dis[u]+w;
		dep[v]=dep[u]+1;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
			son[u]=v;
	}
}
void redfs(int u){
	if(son[u]){
		top[son[u]]=top[u];
		redfs(son[u]);
	}
	for(int i=G.head[u];i;i=G.e[i].nxt){
		int v=G.e[i].v,w=G.e[i].w;
		if(v==son[u])continue;
		top[v]=v;
		redfs(v);
	}
}
void init_dist(){
	dfs(1);
	top[1]=1;
	redfs(1);
}
int LCA(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]])u=fa[top[u]];
		else v=fa[top[v]];
	}
	return dep[u]<dep[v]?u:v;
}
ll dist(int u,int v){
	int lca=LCA(u,v);
	return dis[u]+dis[v]-2*dis[lca];
}
}
using CalLca::LCA;
using Tree::dist;
void dfs(int u,int fa){
	dfn[++cnt_dfn]=u;
	L[u]=cnt_dfn;
	siz[u]=1;
	for(int v:e[u]){
		if(v==fa)continue;
		dis[v]=dis[u]+1;
		Tree::G.AddEdge(u,v,1);
		dfs(v,u);
		siz[u]+=siz[v];
	}
	R[u]=cnt_dfn;
}
bool cmp(int x,int y){
	return L[x]<L[y];
}
int build(int n,int x,ll now){
	sort(a+1,a+n+1,cmp);
	int m=n;
	a[++m]=x;
	for(int i=2;i<=n;i++)
		a[++m]=LCA(a[i],a[i-1]);
	sort(a+1,a+m+1,cmp);
	int tmp=1;
	for(int i=2;i<=m;i++)
		if(a[i]!=a[tmp])a[++tmp]=a[i];
	m=tmp;
	for(int i=1;i<=m;i++){
		ll idx=now+Set.queryrk(rt[L[x]-1],rt[R[x]],a[i]);
		mp[idx]=++Tree::num;
	}
	for(int i=2,lca;i<=m;i++){
		lca=LCA(a[i],a[i-1]);
		ll idxu,idxv;
		idxu=now+Set.queryrk(rt[L[x]-1],rt[R[x]],lca);
		idxv=now+Set.queryrk(rt[L[x]-1],rt[R[x]],a[i]);
		Tree::G.AddEdge(mp[idxu],mp[idxv],dis[a[i]]-dis[lca]);
	}
	return mp[now+Set.queryrk(rt[L[x]-1],rt[R[x]],a[1])];
}
signed main(){
	read(n),read(m),read(q);
	for(int i=2,u,v;i<=n;i++){
		read(u),read(v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	CalLca::init();
	Tree::num=n;
	dfs(1,0);
	rt[0]=Set.build(1,n);
	for(int i=1;i<=n;i++)
		rt[i]=Set.change(rt[i-1],1,n,dfn[i]);
	for(int i=1;i<=m;i++){
		read(opt[i].first),read(opt[i].second);
		if(opt[i].second>n)idx[++tot]=opt[i].second;
	}
	for(int i=1;i<=q;i++){
		read(query[i].first),read(query[i].second);
		if(query[i].first>n)idx[++tot]=query[i].first;
		if(query[i].second>n)idx[++tot]=query[i].second;
	}
	sort(idx+1,idx+tot+1);
	tot=unique(idx+1,idx+tot+1)-idx-1;
	for(int i=1;i<=n;i++)
		mp[i]=i;
	ll now=n;
	for(int i=1,j=1,cnt;i<=m;i++){
		ll x=opt[i].first,to=opt[i].second;
		cnt=0;
		while(j<=tot&&idx[j]<=now+siz[x]){
			int u=Set.querykth(rt[L[x]-1],rt[R[x]],1,n,idx[j]-now);
			a[++cnt]=u;
			j++;
		}
		int tmp=build(cnt,x,now);
		Tree::G.yoAddEdge(mp[to],tmp,1);
		now+=siz[x];
	}
	Tree::init_dist();
	for(int i=1;i<=q;i++){
		ll u=query[i].first,v=query[i].second;
		write(dist(mp[u],mp[v])),Nxt;
	}
}
posted @ 2026-04-13 15:09  陈牧九  阅读(8)  评论(0)    收藏  举报