动态规划 DP 讲解(四)

为了方便,我们叫它 DP


上次我们留了一道题目:P1833 樱花

下面来说说这题该怎么做

  • d p [ j ] dp[j] dp[j] 表示消耗了 j j j 分钟最多可以有多少美学值;
  • a [ i ] a[i] a[i] 表示第i朵花最多可以看多少次;
  • a [ i ] = 0 a[i]=0 a[i]=0 时,用完全背包。
  • 其他时候用多重背包( 01 01 01 可以算是只能一次的多重背包)。

优化

我们可以发现当第 i i i 个朵花,重复第 k k k 01 01 01 背包时,对于第 i i i 朵花 d p [ k ∗ t [ i ] ] dp[k∗t[i]] dp[kt[i]] 前(不包括本身)的值都是已经确定了的,由于前面都是确定的就不要再循环到了。

我们先看第一次 01 01 01 背包时, d p [ t [ i ] ] dp[t[i]] dp[t[i]] 之前的都是可以确定对于第 i i i 朵花,是一定为 0 0 0 的(也就他们的贡献为 0 0 0 ),因为消耗那么多时间根本不够看第 i i i朵花。

所以在第一次 01 01 01 背包中, d p [ 2 ∗ t [ i ] − 1 ] dp[2∗t[i]−1] dp[2t[i]1] 由已经确定的 d p [ t [ i ] − 1 ] dp[t[i]−1] dp[t[i]1] 得来, d p [ 2 ∗ t [ i ] − 2 ] dp[2∗t[i]−2] dp[2t[i]2]由已经确定的 d p [ t [ i ] − 2 ] dp[t[i]−2] dp[t[i]2]得来 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ······ ⋅⋅⋅⋅⋅⋅ d p [ t [ i ] ] dp[t[i]] dp[t[i]] 由已经确定的 d p [ 0 ] dp[0] dp[0] 得来,所以他们在之后的第二次 01 01 01 背包中也是确定的。

第二次01背包与第一次一样由 d p [ 2 ∗ t [ i ] ] dp[2∗t[i]] dp[2t[i]] 之前都是确定的可以得出 d p [ 3 ∗ t [ i ] ] dp[3∗t[i]] dp[3t[i]] 之前都是确定的给第三次01用,这样重复直到次数的上限结束。

代码:

	for(int i=1;i<=n;i++)
	{
		if(a[i]==0)//完全背包和前面一样
		{
			for(int j=t[i];j<=tz;j++) dp[j]=max(dp[j],dp[j-t[i]]+c[i]);
		}
		else
		{
		    for(int l=1;l<=a[i];l++)
		    for(int j=tz;j>=l*t[i];j--) //倒序,前面确定的不要循环到
			{
				dp[j]=max(dp[j],dp[j-t[i]]+c[i]);
			}
		}
	}

完整代码:

#include<bits/stdc++.h>
using namespace std;
int c[100001],a[1000001],t[1000001],te1,te2,ts1,ts2,n,tz;//c[],t[]为题目等于,a[]表示最多看的次数,te1小时1,te2分钟1,ts1小时2,ts2分钟2,tz总时间
int dp[1001];//dp[j]表示消耗了j分钟最多可以有多少美学值
char cc;//符号':'
int main()
{
	cin>>te1>>cc>>te2>>ts1>>cc>>ts2;
	tz=60*(ts1-te1)+ts2-te2;
	cin>>n;
	for(int p=1;p<=n;p++) scanf("%d%d%d",&t[p],&c[p],&a[p]);
	for(int i=1;i<=n;i++)
	{
		if(a[i]==0)
		{
			for(int j=t[i];j<=tz;j++) dp[j]=max(dp[j],dp[j-t[i]]+c[i]);
		}
		else
		{
		    for(int l=1;l<=a[i];l++)
		    for(int j=tz;j>=l*t[i];j--) 
			{
				dp[j]=max(dp[j],dp[j-t[i]]+c[i]);
			}
		}
	}
	cout<<dp[tz];
	return 0;
}


现在我们再来说一说背包问题里面比较杂的一些问题


一、二维费用背包

问题:

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价 1 1 1 和代价 2 2 2 ,第i件物品所需的两种代价分别为 a [ i ] a[i] a[i] b [ i ] b[i] b[i] 。两种代价可付出的最大值(两种背包容量)分别为 V V V U U U。物品的价值为 w [ i ] w[i] w[i]

算法

费用加了一维,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:

f [ i ] [ v ] [ u ] = m a x ( f [ i − 1 ] [ v ] [ u ] , f [ i − 1 ] [ v − a [ i ] ] [ u − b [ i ] ] + w [ i ] ) f[i][v][u]=max(f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]) f[i][v][u]=max(f[i1][v][u],f[i1][va[i]][ub[i]]+w[i])

如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。当物品有如多重背包问题时拆分物品。这里就不再给出伪代码了,相信有了前面的基础,你能够自己实现出这个问题的程序。

有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为 1 1 1,可以付出的最大件数费用为 M M M 。换句话说,设 f [ v ] [ m ] f[v][m] f[v][m] 表示付出费用 v v v 、最多选m件时可得到的最大价值,则根据物品的类型( 01 01 01、完全、多重)用不同的方法循环更新,最后在 f [ 0.. V ] [ 0.. M ] f[0..V][0..M] f[0..V][0..M] 范围内寻找答案。

来看看题:P1855 榨取kkksc03

榨取kkksc03

题目描述

部分题目描述较抽象,可到洛谷上查看

(此处省略无数字)

洛谷的运营组决定,如果一名 OIer 向他的教练推荐洛谷,并能够成功的使用(成功使用的定义是:该团队有 20 20 20 个或以上的成员,上传 10 10 10 道以上的私有题目,布置过一次作业并成功举办过一次公开比赛),那么他可以浪费掉 kkksc03 的一些时间的同时消耗掉 kkksc03 的一些金钱以满足自己的一个愿望。

kkksc03 的时间和金钱是有限的,所以他很难满足所有同学的愿望。所以他想知道在自己的能力范围内,最多可以完成多少同学的愿望?

输入格式

第一行三个整数 n , M , T n,M,T n,M,T,表示一共有 n n n 1 ≤ n ≤ 100 1 \le n \le 100 1n100)个愿望, kkksc03 的手上还剩 M M M 0 ≤ M ≤ 200 0 \le M \le 200 0M200)元,他的暑假有 T T T 0 ≤ T ≤ 200 0 \le T \le 200 0T200)分钟时间。

2 2 2~ n + 1 n+1 n+1 m i m_{i} mi , t i t_{i} ti 表示第 i i i 个愿望所需要的金钱和时间。

输出格式

一行,一个数,表示 kkksc03 最多可以实现愿望的个数。

样例 #1

样例输入 #1

6 10 10
1 1
2 3 
3 2
2 5
5 2
4 3

样例输出 #1

4

这道题是很明显的 01 01 01 背包问题,可是不同的是选一个物品会消耗两种价值(经费、时间),只需在状态中增加一维存放第二种价值即可。

这时候就要注意,再开一维存放物品编号就不合适了,因为容易 MLE。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,M,T,dp[1010][1010];
int m[1010],t[1010];
int main()
{
    scanf("%d%d%d",&n,&M,&T);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&m[i],&t[i]);
        for(int j=M;j>=m[i];j--)
        for(int k=T;k>=t[i];k--)
        {
            dp[j][k]=max(dp[j][k],dp[j-m[i]][k-t[i]]+1);
        }
    }
    printf("%d\n",dp[M][T]);
}

呼,打了很多,有点累了/kk

准备写在这里的东西就移到下次吧qwq

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值