树上前k大的包含不重复结点的长链

一棵树,不一定是二叉树,在每个结点最多只属于一条链的情况下,处理出其中最长的前k个的长度。

最近训练赛做到两道题了,有必要总结一下。

不过我不知道是否有更专门的叫法。

 

借鉴了这位大佬的博客:https://www.cnblogs.com/Aragaki/p/11754534.html

例题1.

2019-2020 ACM-ICPC Brazil Subregional Programming Contest

D - Denouncing Mafia

https://codeforces.com/gym/102346/problem/D

题意就是你要抓一个组织的人,这些组织每个人只知道他直接的一个领导的信息,知道一个人,可以依次向上抓他的领导,给你k次机会,求出能抓住的最多的人数。

显然是一棵树,贪心的找法就是每次找目前来说最长的链,且要考虑结点重复的情况,就需要我们处理出前k大的含不重复结点的长链的长度。

 

算法就是邻接表存这棵树。不知道为啥我第二题比赛时用前向星存树TLE了,换邻接表就成了。

 

ans数组记录当前节点子树里的最长链长为多少 dfs到一个节点 就把除了最长链上的儿子的ans全部push到q里,qq里存的就是除了最长链以外,当前结点作为根节点的子节点所组成链的长度。

我们将1号结点作为整棵树的根节点,最后把ans[1],ans[1]就是最长链的长度, push到q里 取最大的k个即可

为什么这么做是正确的 因为优先队列q里存的是每个节点的父亲节点去掉最长链后自己当根节点时子树的最长链长度

 

 1 #include<bits/stdc++.h>
 2 #define debug(x) cout << #x << ": " << x << endl
 3 typedef long long ll;
 4 using namespace std;
 5 const int MAXN=1e5+7;
 6 vector<int> g[MAXN];
 7 int ans[MAXN];
 8 ll a[MAXN];
 9 priority_queue<int> q;
10 void dfs(int x)
11 {
12     priority_queue<int> qq;
13     ans[x] = 1;
14     for (int v : g[x])
15     {
16         dfs(v);
17         qq.push(ans[v]);
18     }
19     if (g[x].size())
20     {
21         ans[x] = qq.top() + 1;
22         qq.pop();
23         while (qq.size())
24         {
25             q.push(qq.top());
26             qq.pop();
27         }
28     }
29 }
30 int main()
31 {
32     int n, k, x;
33     scanf("%d%d", &n, &k);
34     for (int i = 2; i <= n; i++)
35     {
36         scanf("%d", &x);
37         g[x].push_back(i);
38     }
39     ll anser = 0;
40     dfs(1);
41     q.push(ans[1]);//ans[1]为最长链长度
42     int sz=q.size();
43     for(int i=1;i<=min(k,sz);++i)
44     {
45         int tmp=q.top();
46         anser += tmp;
47         q.pop();
48     }
49     printf("%I64d\n", anser);
50     return 0;
51 }
View Code

第二题

这个好像没地方补。。。

BAPC 2019 The 2019 Benelux Algorithm Programming Contest

A Appeal to the Audience

题意就是分配k个选手到k个叶子节点,每个选手有能力值,层层battle,观众获得的快乐值就是子节点上几个选手能力值的和,然后晋级的是选手能力值中最大的,成为父节点,求出最终最大的得分,题目保证这棵树正好有k个叶子节点。

理解之后发现就是每个选手都可以有贡献,我们肯定要让优秀的选手从更底层上来,对总分产生更大的贡献。

那么就和上面一题一样,我们需要处理出来k个包含不重复节点的最长链,为什么不重复呢?

因为选手相遇了只能晋级一个,只能让一个对接下来的深度产生贡献。

之后对选手能力值排序,贪心的最大的选手,匹配最深的即可。

 

注意到,最长链的长度需要-1,因为最长链包含根节点,手玩一下样例就可以知道,包含根节点长度为4的链产生3层贡献,而其他的链都是不包括根节点的,直接乘能力值即可。

同样邻接表存树,1号节点为根节点,由于题给为0号节点开始,每次++编号即可。

 

 

 1 #include<bits/stdc++.h>
 2 #define debug(x) cout << #x << ": " << x << endl
 3 typedef long long ll;
 4 using namespace std;
 5 const int MAXN=1e5+7;
 6 vector<int> g[MAXN];
 7 int ans[MAXN];
 8 ll a[MAXN];
 9 priority_queue<int> q;
10 void dfs(int x)
11 {
12     priority_queue<int> qq;
13     ans[x] = 1;
14     for (int v : g[x])
15     {
16         dfs(v);
17         qq.push(ans[v]);
18     }
19     if (g[x].size())
20     {
21         ans[x] = qq.top() + 1;
22         qq.pop();
23         while (qq.size())
24         {
25             q.push(qq.top());
26             qq.pop();
27         }
28     }
29 }
30 int main()
31 {
32     int n, k, x;
33     scanf("%d%d", &n, &k);
34     for(int i=1; i<=k; ++i) scanf("%I64d",&a[i]);
35     for (int i = 2; i <= n; i++)
36     {
37         scanf("%d", &x);
38         x++;
39         g[x].push_back(i);
40     }
41     ll anser = 0;
42     dfs(1);
43     q.push(ans[1]);//ans[1]为最长链长度
44     sort(a+1,a+1+k);
45     int t=k;
46     //int sz=q.size();
47     for(int i=1;i<=k;++i)
48     {
49         //debug(q.top());
50         int tmp=q.top();
51         if(i==1) tmp--;
52         anser += (a[t--]*tmp);
53         q.pop();
54     }
55     printf("%I64d\n", anser);
56     return 0;
57 }
View Code

 

然后注意到,这里的q.size()就是叶子节点的数量,与k是一样的,第二题就是覆盖全树,全取完。

猜你喜欢

转载自www.cnblogs.com/Zzqf/p/11809229.html