input:
2
5 4
1 2 1 3
6 2 1 5 7
5 3
1 2 1 3
6 6 1 4 10
output:
54
56
题目大意:
给定一个 n 个节点的有根树,根节点编号为 1。
对于每个节点有价值 s [ i ] 。
现在要选出 k 条简单路径,满足:
- 每条路径都从根节点 1 开始;
- 定义结点i一共被经过了ci次,并且满足对任意一个兄弟结点u,v,|c[u]-c[v]|<=1
定义这k条简单路径的价值为。
求出满足以上两个条件的最大的价值。
解题思路:
首先我们来看根节点1,因为要选出k条路径,并且根据给定的两条性质可知,结点1一定会经过k次,然后再看结点1的子节点,由于第二条性质的存在,设结点1的子节点共有cnt个,那么结点1的子节点至少都得经过k/cnt次,然后由于k/cnt可能不能整除,所以会多出来k%cnt次经过次数,对于剩下的经过次数我们要如何进行选择是一个难点。针对这种情况,我们可以先把当前结点的所有子节点先存放在一个数组中,然后贪心的根据某种判断依据对其进行选择,这种依据其实就是对当前子节点选择增加一次经过次数所带来的价值增益。
根据上述分析我们不难发现,对于每个结点来说,其至少经过的次数其实是固定的,关键就是说能不能再多经过一次的问题。
下面来看一下树形DP的过程:
状态表示:
f[u][0]:表示结点u不会多经过一次多能获得的价值。
f[u][1]:表示结点u多经过一次所能获得的价值。
状态转移:
首先对结点u的所有子节点按照价值增益进行从大到小的排序,设u至少经过的次数为val,u的子节点共有cnt个。
f[u][0]:
对于前val%cnt的子节点,f[u][0]+=f[son][1],对于后面的子节点f[u][0]+=f[son][0]
f[u][1]:
对于前val%cnt+1的子节点,f[u][1]+=f[son][1],对于后面的子节点f[u][1]+=f[son][0],注意这里是val%cnt+1,而不是(val+1)%cnt,因为此时结点u是在原来经过val次的基础上额外增加了一 次经过次数两者是不相等的。
上代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=2e5+10;
int t,n,k;
long long c[N],a[N],h[N],e[N],ne[N],idx,f[N][2];
bool cmp(int a,int b)
{
return (f[a][1]-f[a][0])>(f[b][1]-f[b][0]);
}
void add(int u,int v)
{
e[idx]=v;
ne[idx]=h[u];
h[u]=idx++;
}
void dfs(int u,int val)
{
f[u][0]=val*c[u];
f[u][1]=(val+1)*c[u];
int size=0;
for(int i=h[u];i!=-1;i=ne[i])
{
size++;
}
int cnt=0;
for(int i=h[u];i!=-1;i=ne[i])
{
int j=e[i];
dfs(j,val/size);
}
for(int i=h[u];i!=-1;i=ne[i])
a[++cnt]=e[i];
sort(a+1,a+cnt+1,cmp);
if(cnt)
{
int r=val%cnt;
for(int i=1;i<=cnt;i++)
{
if(i<=r)
f[u][0]+=f[a[i]][1];
else
f[u][0]+=f[a[i]][0];
if(i<=r+1)
f[u][1]+=f[a[i]][1];
else
f[u][1]+=f[a[i]][0];
}
}
}
int main()
{
cin>>t;
while(t--)
{
cin>>n>>k;
idx=0;
memset(h,-1,sizeof h);
for(int i=2;i<=n;i++)
{
int x;
scanf("%lld",&x);
add(x,i);
}
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
dfs(1,k);
cout<<f[1][0]<<endl;
}
return 0;
}