[HNOI2014]世界树

一、题目

点此看题

二、解法

这道题一看就是虚树,我们先把虚树建出来,可惜这道题难点并不在此。
d p [ u ] dp[u] dp[u]表示离 u u u最近的关键点,我们可以用 p a i r pair pair表示 d p dp dp,距离为第一关键字,编号为第二关键字,就可以直接用大于符号比较了,这个 d p dp dp就跑两遍,比较简单。
难点在于统计答案,我们先把离 u u u最近的关键点的答案加上 u u u实际子树的大小,这里分类讨论一下。

  • 如果 u u u的最近关键点和 v v v的最近关键点是一个点,那么这条虚边都贡献给该关键点。
  • o t h e r w i s e otherwise otherwise,我们考虑划分 u , v u,v u,v的势力范围,请看下图:在这里插入图片描述
    我们把这三条边的长度求和,然后除 2 2 2减去 ( v , x v ) (v,x_v) (v,xv)的距离,这里需要特判距离和为奇数的情况,如果这样的话我们就要考虑最中间的点划给谁,这个由编号决定,得到了距离之后我们就倍增求出 v v v管辖的最远点,然后算贡献就行了。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),献上我要调吐了的代码。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#define f first
#define s second
#define mp make_pair
#define pair pair<int,int>
using namespace std;
const int MAXN = 300005; 
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,q,k,k1,k2,tot,cnt,f[MAXN],dep[MAXN],dfin[MAXN],dfou[MAXN];
int ans[MAXN],siz[MAXN],fa[MAXN][20],b[MAXN],a[MAXN*2],vis[MAXN],pd[MAXN];
pair dp[MAXN];
vector<int> G[MAXN];
stack<int> S;
struct edge
{
	int v,next;
}e[2*MAXN];
bool cmp(int a,int b)
{
	int t1=a>0?dfin[a]:dfou[-a];
	int t2=b>0?dfin[b]:dfou[-b];
	return t1<t2;
}
void dfs1(int u,int p)
{
	fa[u][0]=p;
	siz[u]=1;
	for(int i=1;i<20;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	dfin[u]=++cnt;
	dep[u]=dep[p]+1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
	}
	dfou[u]=++cnt;
}
int get(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	if(u==v) return u;
	for(int i=19;i>=0;i--)
		if(fa[u][i]^fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void dfs2(int u,int p)
{
	dp[u].f=0x3f3f3f3f;
	if(pd[u]) dp[u]=mp(0,u);
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==p) continue;
		dfs2(v,u);
		pair t=mp(dp[v].f+dep[v]-dep[u],dp[v].s);
		if(dp[u]>t)
			dp[u]=t;
	}
}
void dfs3(int u,int p)
{
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==p) continue;
		pair t=mp(dp[u].f+dep[v]-dep[u],dp[u].s);
		if(dp[v]>t) dp[v]=t;
		dfs3(v,u);
	}
}
void dfs4(int u,int p)
{
	ans[dp[u].s]+=siz[u];
	for(int i=0;i<G[u].size();i++)
	{
		int v=G[u][i];
		if(v==p) continue;
		dfs4(v,u);
		int len,to,cur;
		if(dp[u].s==dp[v].s)
		{
			ans[dp[u].s]-=siz[v];
			goto In;
		}
		len=dp[u].f+dp[v].f+dep[v]-dep[u]-1;
		to=len/2-dp[v].f,cur=v;
		if(len&1==1 && dp[u].s>dp[v].s)
			to++;
		for(int j=19;j>=0;j--)
			if((1<<j)<=to)
				to-=1<<j,cur=fa[cur][j];
		ans[dp[u].s]-=siz[cur];
		ans[dp[v].s]+=siz[cur]-siz[v];
		In:;
		vis[v]=pd[v]=0;
		G[v].clear();
	}
}
int main()
{
	n=read();
	for(int i=2;i<=n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	dfs1(1,0);
	q=read();
	while(q--)
	{
		k=k1=k2=read();
		for(int i=1;i<=k;i++)
		{
			a[i]=b[i]=read();
			vis[a[i]]=pd[a[i]]=1;
			ans[a[i]]=0;
		}
		sort(a+1,a+1+k,cmp);
		for(int i=1;i<k1;i++)
		{
			int t=get(a[i],a[i+1]);
			if(!vis[t]) a[++k]=t,vis[t]=1;
		}
		if(!vis[1]) a[++k]=1,vis[1]=1;
		k1=k;
		for(int i=1;i<=k1;i++)
			a[++k]=-a[i];
		sort(a+1,a+1+k,cmp);
		for(int i=1;i<=k;i++)
		{
			if(a[i]>0)
				S.push(a[i]);
			else
			{
				int t=S.top();S.pop();
				if(t==1) break;
				G[t].push_back(S.top());
				G[S.top()].push_back(t);
			}
		}
		dfs2(1,0);
		dfs3(1,0);
		dfs4(1,0);
		for(int i=1;i<=k2;i++)
			printf("%d ",ans[b[i]]);
		puts("");
		pd[1]=vis[1]=0;G[1].clear();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值