题目链接:
樱花 - 洛谷https://www.luogu.com.cn/problem/P1833参考了别人的博客:洛谷 P1833 樱花 背包+二进制拆分_一条自私的鱼的博客-CSDN博客
思路:
可以看有限次的樱花树是多重背包,无限次的樱花树是完全背包,完全背包经过优化之后的复杂度和01背包一样,很好处理,但是多重背包在朴素做法下必须加一层循环,复杂度较高。本题用这种朴素做法只能在开了O2优化的情况下ac,朴素做法代码如下。
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll t[maxn], c[maxn], p[maxn]; //时间,美学值,次数(0表示无限次)
ll dp[maxn]; //外层for是遍历物品,内层for是遍历背包(限制时间),dp值是总美学值
signed main(){
//io
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
//input
ll shh, smm, ehh, emm, n; //开始时间,结束时间,樱花树数量
scanf("%lld:%lld", &shh, &smm);
scanf("%lld:%lld", &ehh, &emm);
scanf("%lld", &n);
ll time1 = shh*60 + smm;
ll time2 = ehh*60 + emm;
ll time = time2 - time1;
for(int i=1; i<=n; i++){
scanf("%lld %lld %lld", &t[i],&c[i],&p[i]);
}
//solve and output
memset(dp, 0, sizeof(dp));
for(int i=1; i<=n; i++){ //遍历物品(当前的樱花树)
if(p[i] == 0){ //无限次看
for(int j=t[i]; j<=time; j++){ //多重背包,从左往右遍历背包,即当前限制时间(注意剪枝,从j=t[i]开始,而不是从1开始)
dp[j] = max(dp[j], dp[j-t[i]] + c[i]);
}
}
else{
// 多重背包的技巧,在这一层重复p[i]次循环,就相当于买了1~p[i]次物品取最大总价值
// for(int k=1; k<=p[i]; k++){
// for(int j=time; j>=t[i]; j--){ //和上面一样注意剪枝,还有要从右往左遍历(类似01背包)
// dp[j] = max(dp[j], dp[j-t[i]] + c[i]);
// }
// }
//也可以用下面这种写法(个人认为这种比较好理解,我一般写这种)
for(int j=time; j>=t[i]; j--){
for(int k=1; k<=p[i] && j>=k*t[i]; k++){
dp[j] = max(dp[j], dp[j-k*t[i]] + k*c[i]);
}
}
}
}
cout << dp[time] << endl;
}
下面是二进制优化过的代码写法,经过二进制拆分,可以把多重背包和完全背包都转成01背包来处理,非常的方便(完全背包我直接看成数量足够多的多重背包处理了,虽然复杂度高一些,但是一般问题不大)
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
ll t[maxn], c[maxn], p[maxn]; //时间,美学值,次数(0表示无限次)
ll dp[maxn]; //外层for是遍历物品,内层for是遍历背包(限制时间),dp值是总美学值
ll shh, smm, ehh, emm, n; //开始时间,结束时间,樱花树数量
ll cnt, weight[100000], value[100000]; //分出物品的数量,重量和价值
//二进制拆分板子
inline void pre(){
for(int i=1; i<=n; i++){
int k = 1; //当前物品重量倍数
while(p[i] > 0){ //当物品还没被分完的时候
cnt++; //分出一个物品
weight[cnt] = k*t[i]; //重量(耗时)
value[cnt] = k*c[i]; //价值(美学值)
p[i] -= k; k *= 2; //次数减少k,下次分割的倍数加倍
if(p[i] < k){ //如果剩下的不能再拆成上一份的两倍,就直接放在一起
cnt++;
weight[cnt] = t[i] * p[i]; //剩下的全部重量(耗时)
value[cnt] = c[i] * p[i]; //剩下全部价值(美学值)
break;
}
}
}
}
int main(){
//io
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
//input
scanf("%lld:%lld", &shh, &smm);
scanf("%lld:%lld", &ehh, &emm);
scanf("%lld", &n);
ll time1 = shh*60 + smm;
ll time2 = ehh*60 + emm;
ll time = time2 - time1;
for(int i=1; i<=n; i++){
scanf("%lld %lld %lld", &t[i],&c[i],&p[i]);
if(p[i] == 0) p[i] = 999999;
}
//solve
pre(); //二进制拆分
for(int i=1; i<=cnt; i++){ //遍历所有经过拆分的物品(樱花树)
for(int j=time; j>=weight[i]; j--){ //遍历当前背包(可用时间),01背包模板
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
}
}
cout << dp[time] << endl;
}