写题解原因:
最近编程老师让做一做绿题 dp(可以到我的个人主页看),于是就发现了这道好题,顺便写一写题解.
题目大意:
没看题的走这里 个人觉得题目的描述不太好理解,先总结一下:
给你一棵树,树上每个节点有一个值 a[i]i ,如果他有 a[i]/t 的子节点上传了书信(注意,这里十分重要!),那么它也会上传书信,而对于叶子结点,要给他 a[i] 元钱,他就会上传书信,问你最少花多少元钱能让根节点有 c/t 的子节点上传书信。
思路分析:
由于这是一棵树,我们联想到树上 dp (暴力 dfs ),那么它是否满足 最优子结构和无后效性呢?
最优子结构:
这一点比较好证明,对于一个节点,假设我们已经求出它的所有子节点的最优值,那么我们就能根据子节点求出父节点的最优,符合最优子结构。
无后效性:
无后效性,通俗的讲就是未来与过去无关,现在是对过去的一个总结。那么对于一个节点,无论我们选了它的哪些子节点,只要保证他的值是最优的,那么对于在它上面的节点,只会用到它自己的值,而不会用到它的子节点。
通过以上的分析,我们发现树上dp是可行的,那么状态就可定义为:
dp[i] :节点编号为 i 最少使用多少钱可令他上传书信。
最后还有一个问题,状态的转移:
我们可以将所有子节点的最优值升序排序,并根据公式,取出前“ 子节点数 a_i/tai/t ”的值,这样保证在符合要求的情况下,花最少的钱让该节点上传书信,符合我们 dp 状态的定义。
程序实现:
在读入的同时,要做一件事:建树。
我们可以记录每个节点所有的子节点,方便求值调用。
考虑到这是一棵树,所以我们可以递归求值。
对于每个节点,如果是叶节点,返回a[i]即可。
否则调用它的所有子节点,对这些子节点的最优值排序,求出至少要多少子节点上传书信( 即 a[i]/t ),取排序后前 a[i]/t 个给节点,保证花的钱最少。
代码福利:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=5*1e5;
ll n,t,c;//和题目中意义相同
vector<ll> son[N+10];//记录每个节点的所有孩子
ll a[N+10],dp[N+10];//a和题目一样,dp记录每个节点的最优值(最少花的钱数)
inline double sum(ll x){//求出节点x最少要多少个子节点上传书信
return a[x]*son[x].size()/1.0/t;//注意,这里要除以1.0,才不会自动取整
}
inline void dfs(ll x){//递归求值,做树上dp,x是当前节点的编号
if(son[x].empty()){//没有子节点,说明是叶节点
dp[x]=a[x];//叶节点花a[i]的钱
return;
}
priority_queue<ll,vector<ll>,greater<ll> > q;//小根堆,将所有子节点的最优值自动排序
for(ll i=0;i<son[x].size();i++){//遍历所有子节点
dfs(son[x][i]);//递归调用x的第i个孩子
q.push(dp[son[x][i]]);//将第i个孩子的最优值存入小根堆,自动排序
}
double num=sum(x);//求出最少要几个孩子
for(ll i=0;i<num;i++){//取最小的num个孩子,保证值为最优
dp[x]+=q.top();//加上该孩子花的钱数
q.pop();//用完记得删除,不然会一直记录一个
}
}
int main(){
scanf("%lld%lld%lld",&n,&t,&c);
for(ll i=1;i<=n;i++){
ll node;
scanf("%lld%lld",&node,&a[i]);
son[node].push_back(i);//记录node的孩子
}
a[0]=c; //根节点的值就是c
dfs(0);
printf("%lld\n",dp[0]);
return 0;
}
感谢观看!!!如果能点个赞就更好了,如果能点个关注感激不尽!!!