题意:一棵树有n个结点,n-1条边,每个结点有个权值。每次可以获得从根节点走到叶子结点所有结点的权值和,但是每个结点的权值只能使用一次。求走k次所能获得的最大权值和
思路:首先dfs一次求出所有叶子结点到根结点的权值和,然后从大到小排序,然后根据这个顺序再一次dfs求出每个结点到根节点的权值和,然后再次排序选前k个,证明:每次选择一个叶子结点走到根节点,相当于每次取一条单链,对于有交叉的两条链,先选权值大的肯定是最优的,因为对于某条跟它们没有交叉的链来说,这样子的操作并不会影响到它
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010;
#define LL long long
int n,k;
LL a[maxn],ans[maxn];
int vis[maxn];
vector<int>e[maxn];
struct Node
{
LL sum;
int idx;
bool operator<(const Node&a)const
{
return sum>a.sum;
}
}nodes[maxn];
bool cmp(LL a,LL b)
{
return a>b;
}
LL dfs1(int u)
{
if (vis[u])
return nodes[u].sum;
nodes[u].sum = a[u];
vis[u]=1;
for (int i = 0;i<e[u].size();i++)
{
int v = e[u][i];
nodes[u].sum+=dfs1(v);
}
return nodes[u].sum;
}
LL dfs2(int u)
{
if (vis[u])
return 0;
LL temp = a[u];
vis[u]=1;
for (int i = 0;i<e[u].size();i++)
{
int v = e[u][i];
temp+=dfs2(v);
}
return temp;
}
int main()
{
int T,cas=1;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for (int i = 1;i<=n;i++)
{
scanf("%lld",&a[i]);
nodes[i].idx = i;
}
for (int i = 0;i<=n;i++)
e[i].clear();
memset(vis,0,sizeof(vis));
for (int i = 1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
// e[u].push_back(v);
e[v].push_back(u);
}
for (int i = 1;i<=n;i++)
if (!vis[i])
dfs1(i);
sort(nodes+1,nodes+1+n);
memset(vis,0,sizeof(vis));
for (int i = 1;i<=n;i++)
ans[i] = dfs2(nodes[i].idx);
sort(ans+1,ans+1+n,cmp);
int cnt = 0;
LL anss = 0;
for (int i = 1;i<=n;i++)
{
anss+=ans[i];
cnt++;
if (cnt==k)
break;
}
printf("Case #%d: %lld\n",cas++,anss);
}
}