CodeForces 592D-Super M(树上生成树+树的直径之树DP的缺点)

题目链接:https://codeforces.com/problemset/problem/592/D
博客园食用链接: https://www.cnblogs.com/lonely-wind-/p/13443833.html

Ari the monster is not an ordinary monster. She is the hidden identity of Super M, the Byteforces’ superhero. Byteforces is a country that consists of n cities, connected by n - 1 bidirectional roads. Every road connects exactly two distinct cities, and the whole road system is designed in a way that one is able to go from any city to any other city using only the given roads. There are m cities being attacked by humans. So Ari… we meant Super M have to immediately go to each of the cities being attacked to scare those bad humans. Super M can pass from one city to another only using the given roads. Moreover, passing through one road takes her exactly one kron - the time unit used in Byteforces.
CodeForces  592D-Super M(树上生成树+树的直径之树DP的缺点)
However, Super M is not on Byteforces now - she is attending a training camp located in a nearby country Codeforces. Fortunately, there is a special device in Codeforces that allows her to instantly teleport from Codeforces to any city of Byteforces. The way back is too long, so for the purpose of this problem teleportation is used exactly once.

You are to help Super M, by calculating the city in which she should teleport at the beginning in order to end her job in the minimum time (measured in krons). Also, provide her with this time so she can plan her way back to Codeforces.

Input
The first line of the input contains two integers n and m (1 ≤ m ≤ n ≤ 123456) - the number of cities in Byteforces, and the number of cities being attacked respectively.

Then follow n - 1 lines, describing the road system. Each line contains two city numbers u i u_i ui​ and v i ( 1   ≤   u i ,   v i   ≤   n ) v_i (1 ≤ u_i, v_i ≤ n) vi​(1 ≤ ui​, vi​ ≤ n) - the ends of the road i.

The last line contains m distinct integers - numbers of cities being attacked. These numbers are given in no particular order.

Output
First print the number of the city Super M should teleport to. If there are many possible optimal answers, print the one with the lowest city number.

Then print the minimum possible time needed to scare all humans in cities being attacked, measured in Krons.

Note that the correct answer is always unique.

Examples
Input
7 2
1 2
1 3
1 4
3 5
3 6
3 7
2 7
Output
2
3
Input
6 4
1 2
2 3
2 4
4 5
4 6
2 4 5 6
Output
2
4
Note
In the first sample, there are two possibilities to finish the Super M’s job in 3 krons. They are: 2 → 1 → 3 → 7 2\rightarrow 1\rightarrow 3\rightarrow 7 2→1→3→7 and 7 → 3 → 1 → 2 7\rightarrow 3\rightarrow 1\rightarrow 2 7→3→1→2
However, you should choose the first one as it starts in the city with the lower number.

题目大意:就是给你一棵树,n个点,其中有m个点被占领了,你需要选择一个起点,并沿着树边将其攻陷,问你最少的花费时间,每条边花费的时间为1。输出最小的一个起点和最小的花费时间。

我们画个图可以发现如下:

1.起点一定是m个点中的一个,如果起点不是其中的点,你需要先走到一个点上去,就会多走一步。
2.如果想要走完所有的m个点,则一定会走完一颗子树,这颗子树包含这所有的m个点,换句话说这m个点可以唯一确定一颗子树
3.如果题目要求最后还要返回起点的话,可以观察出需要花费的时间 = 这颗子树的边数 * 2. (也就是子树大小减一再乘以2)
4.现在不需要返回,则可以在这颗子树上找到一条最长的边,也就是这棵树的直径,然后沿着这个直径访问完所有的m个点,这样需要的时间为,子树的边数*2 - 直径的边数.

那么起点也就确定是在直径的两端了。那么现在我们先来确定这个m个点所生成的子树,网上似乎有用虚树的,不过实际上是不用这么麻烦的。我们可以给不在子树中的点打上标记就好了,我们可以观察发现,子树的边界都是黑点(以下都以黑点来代称这m个点),那么我们直接一个dfs递归一下传递个标记就可以将其割裂开来了:

int dfs_tree(int x,int fa)
{
	int flag=0;
	for (auto v:g[x]){
		if (v==fa) continue;
		flag=max(flag,dfs_tree(v,x));//子树的标记传递
	}
	if (broke[x]) {mk[x]=1; return 1;}
	if (flag) {mk[x]=1; return 1;}//如果自己不是黑点,但子树存在
	return 0;
}

然后我们就可以快乐地跑个树的直径了,至于怎么跑限制树的直径,我们也限制一下就好了:

int dfs_d(int x,int fa)
{
	int d1=0,d2=0;
	if (!mk[x]) return 0;
	for (auto v:g[x]){
		if (v==fa) continue;
		if (!mk[v]) continue;//限制,如果不在子树集合中
		sz++;//计算子树的大小
		int p=dfs_d(v,x)+1;
		if (p>d1) {d2=d1; d1=p;}
		else if (p>d2) d2=p;
	}
	d=max(d,d1+d2);
	return d1;
}

然后就没了吗???记住,你还差个起点。。。。然后似乎计算起点有点困难了。。。而这也就是树形DP求树的直径的一个明显的缺点,他没办法求具体的两个端点是什么!!(所以为了图方便的我只学了树DP求直径就卡了挺久的。。。)

树的直径的另一种求法是通过一点 p p p找到其最长的子链,也就是找到离该点最远的那个点 p o s 1 pos1 pos1,然后再通过这个点 p o s 1 pos1 pos1找到另一个离他最远的端点 p o s 2 pos2 pos2。那么也就是说 p o s 1   p o s 2 pos1\ pos2 pos1 pos2就是树的直径上的两个端点了!!

所以说我们求树直径就需要换一种方法了。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=1.5e5+10;

int broke[mac],d=0,mk[mac],sz=1,mist=mac;
int dis[mac],lst;
vector<int>g[mac];

int dfs_tree(int x,int fa)
{
	int flag=0;
	for (auto v:g[x]){
		if (v==fa) continue;
		dis[v]=dis[x]+1;
		flag=max(flag,dfs_tree(v,x));//子树的标记传递
	}
	if (broke[x] || flag){//找最长的链的一端作为下一次dfs的起点
		if (dis[x]>lst || (dis[x]==lst && x<mist)) {lst=dis[x]; mist=x;}
	}
	if (broke[x]) {mk[x]=1; return 1;}
	if (flag) {mk[x]=1; return 1;}//如果自己不是黑点,但子树存在
	return 0;
}

void dfs_d(int x,int fa)
{
	int d1=0,d2=0;
	for (auto v:g[x]){
		if (v==fa) continue;
		if (!mk[v]) continue;
		dis[v]=dis[x]+1;
		if (dis[v]>lst || (dis[v]==lst && v<mist)) {lst=dis[v]; mist=v;}
		sz++;
		dfs_d(v,x);
	}
}

int main(int argc, char const *argv[])
{
	int n,m;
	scanf ("%d%d",&n,&m);
	for (int i=1; i<n; i++){
		int u,v;
		scanf ("%d%d",&u,&v);
		g[u].push_back(v); g[v].push_back(u);
	}
	int st=mac,x;
	for (int i=1; i<=m; i++) scanf ("%d",&x),st=min(st,x),broke[x]=1;
	dfs_tree(st,-1);
	memset(dis,0,sizeof dis);
	st=mist;
	dfs_d(st,-1);
	printf("%d\n%d\n",min(st,mist),(sz-1)*2-lst);
	return 0;
}
上一篇:8.10 不使用新变量交换a,b变量的一个不溢出的方法(非异或)


下一篇:C++类(一)类的定义