题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6867
大意:给定一棵以1为固定顶点的树,你可以从任意一个父亲节点走到其儿子节点。让你新建一条边,这条边上可以双向通行。求可抵达的点对数,主意node[i]可以抵达自身node[i]。
这题一上来就想用贪心(是错的),感觉上好像挺对的。找一条最长的路径,看似可以新抵达的点更多。
实际上此题还与各节点的子树大小具有密切关系。然而我虽然发现了这一点,最后写的搜索竟然TLE。临场还是不行。。第二天写了几分钟就A了。。。
key:
考虑不加边: 每个节点可以去到的点的个数即为其自身,以及不包括自身的子树大小。设节点i的后代个数(子树大小-1)为son[i],则这些点对的个数总和就是n + ∑1,n (son[i])。
第一次dfs_son()填满son[]。
考虑加了一条边:首先,加边的点必定位于某条路径的末端与初始根节点1之间,这点显而易见。加边之后,收益增加的点为这条路径上除了1以外的节点。设有k个好了。那对于这k个节点中编号为i的节点,增加的点对则是 n减去包含其自身的子树大小,也即n-1-son[i]。任务就很明确了。
第二次dfs统计每条路径末端求得的∑k (n-1-son[i]),取其中的最大值,再与1中总和相加即为结果。
*转载请附链接 谢谢
1 #include<iostream>
2 #include<algorithm>
3 #include<cstring>
4 #include<string>
5 #include<cstdio>
6 #include<iomanip>
7 #include<vector>
8 #include<queue>
9
10 #define ll long long
11 using namespace std;
12 const int maxn = 5e5 + 10;
13
14 int n;
15 ll ans = 0;
16
17 int son[maxn];
18 19
20 vector<int> v[maxn];
21
22 int dfs_son(int now)
23 {
24 int cnt = 0;
25 ll tmp = 0;
26 for(int i = 0 ; i < v[now].size() ; i++){
27 int k = v[now][i];
28 cnt++;
29 tmp += dfs_son(k);
30 }
31 if(cnt == 0){
32 son[now] = 1;
33 }else{
34 son[now] = tmp + 1;
35 }
36 return son[now];
37 }
38
39 void dfs_res(int now, bool *vis, ll sum)
40 {
41 if(son[now] == 0) ans = max(ans, sum);
42 for(int i = 0 ; i < v[now].size() ; i++){
43 int nxt = v[now][i];
44 if(vis[nxt] == true) continue;
45 vis[nxt] = true;
46 dfs_res(nxt, vis, sum + n-1-son[nxt]);
47 vis[nxt] = false;
48 }
49 return ;
50 }
51
52 int main(){
53 int T;scanf("%d",&T);
54 while(T--){
55 scanf("%d",&n);
56 ans = 0;
57 for(int i = 0 ; i <= n ; i++){
58 son[i] = 0;
59 v[i].clear();
60 }
61
62 for(int i = 2 ; i <= n ; i++){
63 int father;
64 scanf("%d",&father);
65 v[father].push_back(i);//反向
66 }
67
68 dfs_son(1);//求每个节点的儿子个数
69
70 ll sum = 0;//所有节点的儿子和
71 for(int i = 1 ; i <= n ; i++){
72 son[i] -= 1;
73 sum += son[i];
74 }
75
76 bool vis[maxn];
77 vis[1] = true;
78 dfs_res(1, vis, 0);
79
80 ans += sum + n;
81 printf("%lld
",ans);
82 }
83
84 return 0;
85 }