我是靠谱客的博主 单薄小熊猫,这篇文章主要介绍[codevs 3639] 树的中心---树形DP(树的重心),现在分享给大家,希望可以做个参考。

题目描述 Description

给出一棵树,求出树的中心。

为了定义树的中心,首先给每个结点进行标号。对于一个结点K,如果把K从树中删除(连同与它相连的边一起),剩下的被分成了很多块,每一块显然又是一棵树(即剩下的部分构成了一个森林)。则给结点K所标的号就是森林中结点个数最多的树所拥有的结点数。如果结点K的标号不大于其他任何一个结点的标号,则结点K被称为是树的中心。

输入描述 Input Description

输入:

输入的第一行包含一个整数N(1≤N≤16 000),表示树中的结点数。接下来N-1行,每个两个整数a,b,由一个空格分隔,表示a与b之间有一条边。

输出描述 Output Description

输出:

输出两行,第一行两个整数v,T,v表示树的中心结点的标号,T表示树有多少个中心。第二行包含T个数,为所有树的中心的编号,按升序排列。

样例输入 Sample Input

样例输入:

7

1 2

2 3

2 4

1 5

5 6

6 7

样例输出 Sample Output

样例输出:

3 1

1

数据范围及提示 Data Size & Hint

数据范围: 20% N<=100 100% N<=16 000

分析

幸好之前在书上有看到过相关思路,so水过.(不要被事物的表面现象所迷惑),翻译一下题目,就是求一个点使以它为树根,满足最大子树的节点数最小.其实就是树的重心

思路:

  1. 以1为根建树,在回溯时顺便求出子树的节点大小与子树孩子的最大值
  2. 得出以上条件后可稍加分析,会发现一个特点:
    当以R为根节点的树转化为以R的孩子H为根节点时,只要令size[R]=n-size[H],size[H]=n即可维护子树的大小
  3. So 题目要求的K即可表示为 K=max{ n-size[now],max( size[j] ) }
    其中,now为当前节点,j为now的children
  4. 充分利用dfs的特性,在now节点处理完它的孩子后,直接用 3 的等式,更新ans

代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include <cstdio> #include <cstdlib> #include <vector> #include <algorithm> #define open(s) freopen(s".in","r",stdin); freopen(s".out","w",stdout); #define close fclose(stdin); fclose(stdout); using namespace std; struct node //树的节点 { vector<int>ch; int d; // size int ma; //子树孩子的最大值 }; struct Edge //邻接表(链式向前星) { int to; int next; }; int ank; // 找K值 vector<int>ans; //记录节点 int n; int cnt; int head[16005]; Edge edge[32005]; node tree[16005]; bool vis[16005]; inline int read() { int k=1; int sum=0; char c=getchar(); for(;'0'>c || c>'9' ;c=getchar()) if(c=='-') k=-1; for(;'0'<=c && c<='9';c=getchar()) sum=sum*10+c-'0'; return sum*k; } inline void write(int x) { if(x<0) { putchar('-'); x*=-1; } if(x>9) write(x/10); putchar(x%10+'0'); } inline void add(int x,int y) { ++cnt; edge[cnt].to=y; edge[cnt].next=head[x]; head[x]=cnt; } inline int max(int x,int y) { return x>y?x:y; } inline void build(int p) { for(int i=head[p];i;i=edge[i].next) // 建树 if(!vis[edge[i].to]) { int y=edge[i].to; vis[y]=1; //初始化 tree[y].d=1; tree[p].ch.push_back(y); build(y); tree[p].d+=tree[y].d; //更新d/ma tree[p].ma=max(tree[p].ma,tree[y].d); } tree[p].ma=max(tree[p].ma,n-tree[p].d); // 更新答案 if(ank==-1 || tree[p].ma<ank) { ank=tree[p].ma; ans.clear(); ans.push_back(p); }else if(tree[p].ma==ank) ans.push_back(p); } inline bool cmp(int x,int y) { return x<y; } int main() { open("tree"); n=read(); for(int i=1;i<n;++i) { int x=read(),y=read(); add(x,y); add(y,x); } vis[1]=1; //初始化 tree[1].d=1; ank=-1; build(1); sort(ans.begin(),ans.end(),cmp); //答案要求 int s=ans.size(); write(ank);putchar(' ');write(s);putchar('n'); for(int i=0;i<s;++i) { write(ans[i]); putchar(' '); } close; return 0; }

补充

树的重心相关知识点:

定义:使得最大子树的节点数最小的点
性质
1.树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。
2.把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上
3.把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离
重要意义
在对树进行分治的时候可以避免N^2的极端复杂度(从退化链的一端出发),保证NlogN的复杂度

最后

以上就是单薄小熊猫最近收集整理的关于[codevs 3639] 树的中心---树形DP(树的重心)的全部内容,更多相关[codevs内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(66)

评论列表共有 0 条评论

立即
投稿
返回
顶部