【洛谷】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