jzoj3501-消息传递【换根法,树形dp】

正题

题目链接:https://jzoj.net/senior/#contest/show/3005/1


题目大意

一棵树,一个信息开始给一个人,每次得到信息的人可以选择相邻节点中的一个传递,求最短多久可以传到所有人。


解题思路

我们先考虑如何求一根的答案, f a r i far_i 表示第 i i 个点的传递玩子树所需要的最短时间,显然对于子节点的 f a r far 排个序依次传递就好了。

之后考虑换根,对于一个节点我们要求出从这个父节点不需要传递该节点时的最短时间 u p i up_i ,这个值在父节点操作时可以求出。

对于 d p dp 到的节点 x x ,我们将子节点的 f a r far u p x up_x 丢进去排序,然后用一个前缀 m a x max 和一个后缀 m a x max 求出来所有子节点的 u p up 还可以顺便求出该点答案。

时间复杂度 : O ( n log n ) :O(n\log n)


c o d e code

注意这里并没有使用 u p up 数组,而是覆盖掉 f a r far 数组来使用

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=2e5+10;
struct node{
	int to,next;
}a[N*2];
int tot,ls[N],pre[N],aft[N];
int n,far[N],ans[N],answer;
vector<int> q[N];
void addl(int x,int y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;
	return;
}
bool cmp(int x,int y)
{return far[x]>far[y];}
void dfs(int x,int fa){
	int maxx=0,maxy=0;
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa) continue;
		dfs(y,x);q[x].push_back(y);
	}
	sort(q[x].begin(),q[x].end(),cmp);
	for(int i=0;i<q[x].size();i++)
		far[x]=max(far[q[x][i]]+i+1,far[x]);
	return;
}
int dp(int x,int fa){
	if(fa)q[x].push_back(x);
	sort(q[x].begin(),q[x].end(),cmp);
	for(int i=1;i<=q[x].size();i++)
		pre[i]=max(pre[i-1],far[q[x][i-1]]+i);
	for(int i=q[x].size();i>=1;i--)
		aft[i]=max(aft[i+1],far[q[x][i-1]]+i);
	ans[x]=max(ans[x],pre[q[x].size()]);
	answer=min(answer,ans[x]);
	for(int i=0;i<q[x].size();i++){
		int y=q[x][i],z;
		far[y]=max(pre[i],aft[i+2]-1);	
	}
	for(int i=ls[x];i;i=a[i].next){
		int y=a[i].to;
		if(y==fa) continue;
		dp(y,x);
	}
}
int main()
{
	freopen("news.in","r",stdin);
	freopen("news.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x;scanf("%d",&x);
		addl(i,x);addl(x,i);
	}
	answer=2147483647;
	dfs(1,1);
	dp(1,0);
	printf("%d\n",answer+1);
	for(int i=1;i<=n;i++)
		if(ans[i]==answer)
			printf("%d ",i);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Mr_wuyongcong/article/details/104160604