模板:虚树

所谓虚树,就是虚了的树

(逃)

前言

在有些时候,我们只关心树上的某些特殊点,问题中的整体规模较大,但是这些特殊点往往比较稀疏,这个时候就可以使用虚树,只保留树上的关键点,从而大大缩小问题规模

引例

洛谷P3233 世界树

给出一个树和m个询问,每次询问给出k个特殊点,树上的每个结点都受最近的特殊点管辖,求每个特殊点管辖的结点数目
数据范围: n ≤ 3 ∗ 1 0 5 , m ≤ 3 ∗ 1 0 5 , ∑ k ≤ 3 ∗ 1 0 5 n\leq3*10^5,m\leq3*10^5,\sum k \leq3*10^5 n3105,m3105,k3105

解析

首先考虑只有一次询问的时候如何做
只有我想到bfs了吗…
可以换根dp很简单的解决
但是本题每次dp一遍是nm的,无法接受

考虑使用虚树
首先,我们要找到所有的关键点
除了特殊点之外,我们还要选出一些非特殊点以保持树的形态不变
经过观察和题解,发现两两求出lca作为关键点即可
但是两两求是 k 2 k^2 k2的,无法接受
这里只有我想到根号分治了吗

引理:若x、y、z是dfs序单调递增的三个点,那么 l c a ( x , z ) lca(x,z) lca(x,z)必定是 l c a ( x , y ) lca(x,y) lca(x,y) l c a ( y , z ) lca(y,z) lca(y,z)两者之一

证明…自己画画图吧
所以我们把关键点按dfs序排一下序,然后相邻求lca即可

求出关键点,我们还要在它们之间连边
这可以在确定关键点的时候顺便求出来
具体的实现,需要利用一个来进行
栈里存储的是一条当前的极右链
一本通的实现比较恶心,学习了洛谷上第一篇题解的写法,相对比较简洁

zhan[top=1]=v[1];
for(int i=2;i<=k;i++){
	int x=v[i],lca=Lca(x,zhan[top]);
	while(top>1&&dep[lca]<=dep[zhan[top-1]]){
		addline(zhan[top-1],zhan[top]);top--;
	}
	if(zhan[top]!=lca){
		addline(lca,zhan[top]);zhan[top]=lca;
	}
	zhan[++top]=x;
}
while(top>1) addline(zhan[top-1],zhan[top]),top--;
int rt=zhan[1];

然后就是dp部分
不在虚树上的点可以比较容易的确定归属
关键是在链两端所属不同时的链上的点
可以找到链上管辖示例的分界点
然后用子树大小减一减就出来了
还是不难理解的,具体看代码吧
写的有点恶心,但跑的还是挺快的

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf (n+1)
//#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=3e5+100;
const int M=2e5+10500;
const double eps=1e-5;
inline ll read(){ll x(0),f(1);char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

int n,m;
int tim;
struct node{
	int to,nxt,w;
}p[N<<1];
int fi[N],cnt;
int jd[N];
inline void addline(int x,int y,int w){
	if(jd[x]!=tim){
		jd[x]=tim;fi[x]=-1;
	}
	if(jd[y]!=tim){
		jd[y]=tim;fi[y]=-1;
	}
	//printf("x=%d y=%d\n",x,y);
	p[++cnt]=(node){y,fi[x],w};fi[x]=cnt;
	return;
}

int pos[N];
int sz[N],pl[N][20],dep[N];
void init(int x,int f){
	pl[x][0]=f;
	for(int k=1;pl[x][k-1];k++) pl[x][k]=pl[pl[x][k-1]][k-1];
	pos[x]=++tim;sz[x]=1;
	dep[x]=dep[f]+1;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(to==f) continue;
		init(to,x);
		sz[x]+=sz[to];
	}
	return;
}
inline int Lca(int x,int y){
	if(pos[x]<=pos[y]&&pos[y]<=pos[x]+sz[x]-1) return x;
	for(int k=19;k>=0;k--){
		int o=pl[x][k];
		if(!o||(pos[o]<=pos[y]&&pos[y]<=pos[o]+sz[o]-1)) continue;
		x=o;
	}
	return pl[x][0];
}
int bac[N],tag[N],v[N],ori[N];
int fa[N],zhan[N],top;
int dis[N],id[N];
bool cmp(int x,int y){
	return pos[x]<pos[y];
}
void dfs1(int x){
	if(tag[x]==tim){
		dis[x]=0;id[x]=x;
	}
	else dis[x]=1e9;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		dfs1(to);
		if(dis[x]>dis[to]+p[i].w||(dis[x]==dis[to]+p[i].w&&id[x]>id[to])){
			dis[x]=dis[to]+p[i].w;id[x]=id[to];
		}
	}
}
void dfs2(int x,int f,int fv){
	if(f){
		if(dis[x]>dis[f]+fv||(dis[x]==dis[f]+fv&&id[x]>id[f])){
			dis[x]=dis[f]+fv;id[x]=id[f];
		}
	}
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to;
		dfs2(to,x,p[i].w);
	}
	//printf("x=%d dis=%d id=%d\n",x,dis[x],id[x]);
	return;
}
inline int jump(int x,int w){
	for(int k=19;k>=0;k--){
		if(w<(1<<k)) continue;
		w-=(1<<k);x=pl[x][k];
	}
	return x;
}
void calc(int x){
	bac[id[x]]++;
	int lft=sz[x]-1;
	for(int i=fi[x];~i;i=p[i].nxt){
		int to=p[i].to,son=jump(to,p[i].w-1);
		lft-=sz[son];
		//printf("  x=%d to=%d -=%d+%d\n",x,to,siz[to],p[i].w-1);
		if(id[x]==id[to]) bac[id[x]]+=sz[son]-sz[to];
		else{
			int d=p[i].w+dis[to]-dis[x],add=(d-1+(id[x]<id[to]))/2;
			int pl=jump(to,p[i].w-1-add);
			bac[id[x]]+=sz[son]-sz[pl];
			bac[id[to]]+=sz[pl]-sz[to];
		//	printf("  x=%d to=%d add=%d x+=%d to+=%d\n",x,to,add,sz[son]-sz[pl],sz[pl]-sz[to]);
		}
		calc(to);
	}
	//printf("x=%d leftsiz=%d\n",x,lft);
	bac[id[x]]+=lft;
}
int main(){
#ifndef ONLINE_JUDGE
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
#endif
	memset(fi,-1,sizeof(fi));cnt=-1;
	n=read();
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		addline(x,y,1);addline(y,x,1);
	}
	init(1,0);
	m=read();
	for(tim=1;tim<=m;tim++){
		//printf("\ntim=%d\n",tim);
		int k=read();
		for(int i=1;i<=k;i++){
			v[i]=ori[i]=read();
			bac[v[i]]=0;tag[v[i]]=tim;fa[v[i]]=0;
		}
		sort(v+1,v+1+k,cmp);
		cnt=-1;
		zhan[top=1]=v[1];
		for(int i=2;i<=k;i++){
			int x=v[i],lca=Lca(x,zhan[top]);
			while(top>1&&dep[lca]<=dep[zhan[top-1]]){
				addline(zhan[top-1],zhan[top],dep[zhan[top]]-dep[zhan[top-1]]);top--;
			}
			if(zhan[top]!=lca){
				addline(lca,zhan[top],dep[zhan[top]]-dep[lca]);zhan[top]=lca;
			}
			zhan[++top]=x;
		}
		while(top>1) 
			addline(zhan[top-1],zhan[top],dep[zhan[top]]-dep[zhan[top-1]]),top--;
		//printf("OK\n");
		int rt(zhan[1]);
		dfs1(rt);
		dfs2(rt,0,0);
		bac[id[rt]]+=n-sz[rt];
		calc(rt);
		for(int i=1;i<=k;i++) printf("%d ",bac[ori[i]]);
		putchar('\n');
	}
	return 0;
}
/*
10
2 1
3 2
4 3
5 4
6 1
7 3
8 3
9 4
10 1
1
5
2 9 3 5 8
*/
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值