其他分享
首页 > 其他分享> > 【洛谷】P1987 摇钱树(贪心+dp)

【洛谷】P1987 摇钱树(贪心+dp)

作者:互联网

原题链接

题意

给定 \(n\) 棵摇钱树,第 \(i\) 棵摇钱树上的初始金币为 \(a_i\),每天会掉下 \(b_i\) 个金币。在 \(k\) 天内,每天都可以砍一棵摇钱树,第 \(i\) 天砍第 \(j\) 棵摇钱树可以得到 \(a_j-(i-1)*b_j\) 个金币,求最终得到的金币数量的最大值。

思路

首先可以将题目中的 \(k\) 天看成是一个容量为 \(k\) 的背包,第 \(j\) 棵摇钱树看成是一个体积为 \(1\),价值为 \(\max(a_j-(i-1)*b_j,0)\) 的物品。其中 \(i\) 表示该物品是第 \(i\) 个放进背包的物品。那么也就可以将题目转化为一个 经典的 \(01\) 背包问题。

但是由于有 \((i-1)\) 这个变量在影响价值,所以枚举物品放进背包的顺序不同,答案也会不同。所以需要先按照 \(b_i\) 的大小对物品进行排序,再进行状态转移。这是因为本题满足一个贪心性质:在放同样多的物品的所有方案中,先放 \(b_i\) 的物品的方案最优。


证明:

设当前背包中已经放了 \(p\) 个物品,现在要放 \(i\) 和 \(j\) 两个物品。

先放物品 \(i\) 的价值:\(f[p]+a_i-(p-1)*b_i+a_j-p*b_j\)------ ①。

先放物品 \(j\) 的价值:\(f[p]+a_j-(p-1)*b_j+a_i-p*b_i\)------ ②。

① 式减去 ② 式,可以得到:\(b_i-b_j\)。

当 \(b_i>b_j\) 时,先放物品 \(i\) 更优;当 \(b_i<b_j\) 时,先放物品 \(j\) 更优。

故同样放 \(i\) 个物品时,先放 \(b_i\) 大的物品更优。


需要注意的是,最终的答案不一定为 \(f[k]\),如下面这组数据:

3 3
1000000 2 1
1000 10000 111111

显然在这组数据中,先一个物品时最优。但是上面的贪心性质就决定了把要所有物品都放进去时,不可能先放第一个物品。而在所有方案中,满足第一个物品最先放进去的方案,就只有 \(f[1]\) 所以在这组样例中的答案就是 \(f[1]\)。

由这组样例可以发现,如果要放所有物品,就有可能会放价值已经降到 \(0\) 的物品,而白白浪费了后面价值更大的物品。所以不一定放所有的物品最优。

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
int n,k,f[N];
struct node{
	int m,b;
	bool operator <(const node &t)const{
	    return b>t.b;
	}
}tr[N];
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int main()
{
	while(scanf("%d%d",&n,&k),n)
	{
		memset(f,0,sizeof(f));
		int ans=0;
		for(int i=1;i<=n;i++) scanf("%d",&tr[i].m);
		for(int i=1;i<=n;i++) scanf("%d",&tr[i].b);
		sort(tr+1,tr+n+1);
		for(int i=1;i<=n;i++)
		    for(int j=min(k,n);j>=1;j--)
		        f[j]=max(f[j],f[j-1]+max(tr[i].m-(j-1)*tr[i].b,0));
		for(int i=1;i<=k;i++) ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
	return 0;
}

标签:背包,洛谷,int,max,P1987,摇钱树,物品,先放,dp
来源: https://www.cnblogs.com/NLCAKIOI/p/14957355.html