bzoj 4247 //4247: 挂饰

bzoj 4247 //4247: 挂饰   //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=4247

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

//4247: 挂饰
//在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=4247
//题目反复阅读,加上说明,弄明白了,手机上可以挂0个挂件,也可以挂1个挂件.
//背包有些奇特,容量(即挂钩数)是可以变化的.
//此文https://www.cnblogs.com/2014nhc/p/6231288.html思路不错,摘抄如下
/*
题解:我表示这道题是在老师和同学耗尽心思的讲解中才勉强懂了一点却因为输入输出调了十遍RE。。。

不过很开心的是这道题在他人的讲解和我看题解的过程中让我对动归的理解比以前稍微好了一点(虽然还是很菜),希望能有更多提升。

好啦看题目,这道题其实就是背包的容量在不停的变化,用f[i][j]来表示挂完第i个物品后还剩j个挂钩,那么转移方程为f[i][j]=max(f[i-1][j],f[i-1][max(j-a[i],0)+1]+b[i]);

为什么这么写呢?解释一下:首先f[i][j]=f[i-1][j]很好懂就是指现在这个物品不挂,可取得的喜悦值,那么f[i-1][max(j-a[i],0)+1]是什么意思呢,我们先看j-a[i]+1,这是指上一次的挂钩值,那么后面的这个式子意思是把这个饰件挂上的喜悦值+上一次的喜悦值,但是j-a[i]是有可能小于0的,即如果第i个物品挂上去的话这次不可能就j个挂钩,这个时候我们就假设上一次只剩了一个挂钩,这样j个挂钩都是现在第i个物品带来的,即使j-a[i]<0,也可以当做我们把多余j的挂钩给舍去了。为什么我们要假设上一次只剩了一个挂钩呢?因为当前物品如果能挂上去,说明上一次多出的挂钩数一定大于等于1,却不能保证大于1,所以我们至多只能设上次只剩了一个挂钩(只有一是能被确定的),这就是这个式子让我当时最不理解的地方。。。。。

好啦,第二个重点,在做动归前我们需要将a数组排一下序,为何因为我们会发现如果不排序的话我们这次挂上这个饰品,即使j是负数也并不是不合法的,因为挂饰间可以互换位置,只要后面挂饰的挂钩能够把j在最后补成自然数就可以了,那样就会导致动归的循环不确定,没法一步步推,所以要进行排序。
*/
//关于max(j-a[i],0)+1此文https://www.cnblogs.com/cjyyb/p/9669181.html解释得不错,摘抄如下
/*
要么选,那么首先这个物品至少要占用一个挂钩,然后它会贡献a[i]个挂钩,事实上如果a[i]之和太大那么和n没有区别
*/
//挂钩数量排序,此文https://www.cnblogs.com/Patrick-X/p/6252720.html介绍得不错,摘抄如下
/*
但是有些时候,j-a[i]+1为负数也可能有意义。因为挂钩的位置没有要求,只要后来的挂钩能把j补成非负数就是合法的,但如果去考虑这个,动规的循环变量就不确定,所以要把挂饰按能提供的挂钩数量从大到小排序,就能避免这种情况。
*/
//对样例的手动模拟过程如图所示,不得不说,还是计算机精准,手动模拟有些错误,已更正,并红色标出,下图是正确数据。

//有了清晰的过程,编码就容易了
//别忘了,再对样例进行检验,看输出的中间数据,是否与手动模拟数据一致.
//对应样例,程序生成中间数据如下
/*
5
-2122219135 0 -2122219135 -2122219135 -2122219135 -2122219135
-2 0 -2 -2122219135 -2122219135 -2122219135
-1 0 -2 -2122219135 -2122219135 -2122219135
4 2 -2 -2122219131 -2122219131 -2122219131
4 2 -2 -2122219130 -2122219130 -2122219131
5 2 -2 -2122219127 -2122219128 -2122219131
*/
//样例通过,提交AC.2019-8-31 17:55
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 2010
using namespace std;
int dp[maxn][maxn];//dp[i][j]表示 处理了前i件物品,所剩挂钩数j,对应的喜悦值
struct node{
    int a,b;
}g[maxn];
int max(int a,int b){
    return a>b?a:b;
}
int cmp(node a,node b){
    return a.a>b.a;//俺挂钩数量自小到大排序
}
int main(){
    int i,j,n,ans;
    memset(dp,-127,sizeof(dp)),dp[0][1]=0,ans=dp[0][0];//重置dp最小值,手机自带1个挂钩,不挂挂件时,喜悦值是0
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d%d",&g[i].a,&g[i].b);
    sort(g+1,g+1+n,cmp);
    for(i=1;i<=n;i++)
        for(j=0;j<=n;j++)
            dp[i][j]=max(dp[i-1][j],dp[i-1][max(j-g[i].a,0)+1]+g[i].b),ans=max(ans,dp[i][j]);
    printf("%d\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值