洛谷P3408 恋爱 题解

写题解原因:

最近编程老师让做一做绿题 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;
}

感谢观看!!!如果能点个赞就更好了,如果能点个关注感激不尽!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值