BZOJ 3572: [Hnoi2014]世界树

好久没有做虚树了,感觉好虚啊

首先建出虚树,然后对于两边dp搞出虚树上每个点连接到的居委会

对于虚树上的每条边,我们找到两端点的分界点,再对他们连接到的居委会更新答案

这里给树剖加个特技就能求两点间第K个点了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300000+5;
const int inf=1e9;
int read(){
	char ch=getchar();int x=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Edge{int to,next;}e[N<<1];
int head[N],cnt;
void ins(int u,int v){
	if(u==v)return;
	e[++cnt]=(Edge){v,head[u]};head[u]=cnt;
}
void insert(int u,int v){
	ins(u,v);ins(v,u);
}
bool is[N];
int dep[N],fa[N],siz[N],son[N],top[N],pos[N],node[N],sz,dfn[N],dfs_clock;
void dfs(int u){
	siz[u]=1;son[u]=0;dfn[u]=++dfs_clock;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;if(v==fa[u])continue;
		fa[v]=u;dep[v]=dep[u]+1;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs(int u,int tp){
	top[u]=tp;pos[u]=++sz;node[sz]=u;
	if(son[u])dfs(son[u],tp);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		if(v!=fa[u]&&v!=son[u])dfs(v,v);
	}
}
bool cmp(int u,int v){
	if(dep[u]==dep[v])return u<v;
	return dep[u]<dep[v];
}
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 min(u,v,cmp);
}
int dis(int u,int v){
	if(!u||!v)return inf;
	return dep[u]+dep[v]-2*dep[lca(u,v)];
}
int lin[N],f[N];
void dp1(int u){
	f[u]=0;
	if(is[u])lin[u]=u;
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		dp1(v);lin[u]=min(lin[u],lin[v],cmp);
	}
}
void dp2(int u){
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		int t1=dis(lin[e[i].to],e[i].to),t2=dis(lin[u],e[i].to);
		if(t1==t2)lin[e[i].to]=min(lin[e[i].to],lin[u]);
		else if(t2<t1)lin[e[i].to]=lin[u];
		dp2(v);
	}
}
int size_chain(int u){
	return dep[u]-dep[top[u]]+1;
}
int iabs(int x){
	return x<0?-x:x;
}
int size_chain(int u,int v){
	return iabs(dep[u]-dep[v])+1;
}
int size(int u,int v){
	return dis(u,v)+1;
}
int find(int u,int k){
	if(k<=size_chain(u))return node[pos[u]-k+1];
	else return find(fa[top[u]],k-size_chain(u));
}
int find(int u,int v,int k){
	int w=lca(u,v);
	if(k<=size_chain(u,w))return find(u,k);
	else return find(v,size(u,v)-k+1);
}
int st[N],h[N],b[N];
bool cmp1(int u,int v){
	return dfn[u]<dfn[v];
}
int work(int u,int v){
	int x=lin[u],y=lin[v],mid=find(y,x,size(x,y)>>1),t=find(u,v,2);
	if((size(x,y)&1)&&y<x)mid=fa[mid];
	if(size(x,mid)<=size(x,u))mid=t;
	else if(size(y,mid)<=size(y,v))mid=v;
	f[x]+=siz[t]-siz[mid];f[y]+=siz[mid]-siz[v];
		
}
void calc_edge(int u){
	f[lin[u]]+=siz[u];
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].to;
		f[lin[u]]-=siz[find(u,v,2)];
		work(u,v);
		calc_edge(v);
	}
	head[u]=0;lin[u]=0;
}
void solve(){
	int k=read();
	for(int i=1;i<=k;i++){
		h[i]=read();is[h[i]]=true;
		b[i]=h[i];
	}
	sort(h+1,h+1+k,cmp1);
	int tp;cnt=0;
	st[tp=1]=1;
	for(int i=1;i<=k;i++){
		int now=h[i],f=lca(now,st[tp]);
		if(f==st[tp]){st[++tp]=now;continue;}
		while(tp>1&&f==lca(now,st[tp-1])){
			ins(st[tp-1],st[tp]);tp--;
			f=lca(now,st[tp]);
		}
		ins(f,st[tp]);
		st[tp]=f;st[++tp]=now;
	}
	while(--tp)ins(st[tp],st[tp+1]);
	dp1(1);dp2(1);calc_edge(1);
	for(int i=1;i<=k;i++){
		printf("%d ",f[b[i]]);
		is[b[i]]=false;
	}
	putchar('\n');
}
int main(){
	//freopen("a.in","r",stdin);
	int n=read();
	for(int i=1;i<n;i++){
		int u,v;u=read();v=read();insert(u,v);
	}
	dfs(1);dfs(1,1);dep[0]=inf;
	memset(head,0,sizeof(head));
	int q=read();
	while(q--)
	solve();
	return 0;
}
突然发现是RANK6哎,果然树剖就是快

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值