题目大意:有一棵n个点,以1为根节点的有权树(权值为si),现要求找出k条从1开始的简单路,并且树中每两个有相同父结点的点被包含进的路径数的差不能大于1,求所有选择的路径的权值和的最大值
1<=2e5<=n;1<=k<=1e9;1<=si<=1e4
思路:按照题目的要求,我们走过一条路径后,如果想要再走一遍这条路径,就得把与这条路径上的点父结点相同的点所在的路径走一遍,所以我们从根节点出发每棵子树可以走的次数为当前的k/当前点的子树数量,每个点的贡献也就是这个点的权值*走过的次数,当前走过的次数在递归时用k维护,同时维护每棵子树的贡献,然后如果k不能整除,就将所有子树的贡献排序,优先选大的,然后剩下没选的第一个做备选,如果还需要有路径走过这个点,就加上这棵备选树的贡献。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
vector<int>v[N];
ll a[N], sum[N];
bool cmp(int a, int b)
{
return sum[a] > sum[b];
}
ll dfs(int u, int k)
{
ll ret = a[u] * k;//当前点的贡献
int cnt = v[u].size();
for (int i = 0; i < cnt; i++)
{
ret += dfs(v[u][i], k / cnt);//后序遍历
}
if (cnt)
{//对于每个出度大于1的节点,贡献要加上最大的可选子树
sort(v[u].begin(), v[u].end(), cmp);//对每棵子树的贡献从大到小排序
cnt = k % cnt;//还可以选的子树数量
for (int i = 0; i < cnt; i++)
{
ret += sum[v[u][i]];//每个可选子树得到贡献
}
sum[u] = a[u] + sum[v[u][cnt]];//如果需要再次走到这个节点,那么有贡献的子树为没有访问过的第一棵
}
else
{
sum[u] += a[u];//如果出度为1,那就维护所在子树的贡献
}
return ret;
}
void init(int n)
{
for (int i = 1; i <= n; i++)
{
v[i].clear();
sum[i] = 0;
}
}
int main()
{
int t;
cin >> t;
while (t--)
{
ll n, k;
scanf("%lld%lld", &n, &k);
init(n);
for (int i = 1; i < n; i++)
{
int u;
scanf("%d", &u);
v[u].push_back(i + 1);//邻接表存图,便于对子树排序和统计每个点的子树数量
}
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
printf("%lld\n", dfs(1, k));
}
return 0;
}