为了方便,我们叫它 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[k∗t[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[2∗t[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[2∗t[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[2∗t[i]] 之前都是确定的可以得出 d p [ 3 ∗ t [ i ] ] dp[3∗t[i]] dp[3∗t[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[i−1][v][u],f[i−1][v−a[i]][u−b[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 1≤n≤100)个愿望, kkksc03 的手上还剩 M M M( 0 ≤ M ≤ 200 0 \le M \le 200 0≤M≤200)元,他的暑假有 T T T( 0 ≤ T ≤ 200 0 \le T \le 200 0≤T≤200)分钟时间。
第 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