背包问题
一不小心,背包问题就结束了呢,刚学完acwing里的背包,得看书和找个问题巩固下
其实背包问题就是最基本的dp,最主要的状态转移方程要弄清楚,每一步选什么,怎么选,选几个都是很重要的问题
在这里我要diss我自己,明明多重背包学过了,后来做了一道题,一个循环的方式和里面的明明很像,做错了,hhh....
功夫不到家,dp的题要多练,没办法,多多积累经验吧
好,言归正传
学背包的时候,从二维开始入手,弄清楚每一步的意思,然后通过一维来优化,分析嘛,可以用闫式分析法,嘻嘻
01背包:
就是有n个物品和m的背包容量,告诉你价值,每个物品只能选一次,问求得的最大价值:
这里的每个物品只能选一次很关键啊,这里决定了到底用的是正序还是逆序,(具体为什么看之前的博客hhh)
所以咱们就先把所有的情况枚举下来,一个个来看
从第一个开始判断选还是不选,第二个再判断选还是不选,第三个再判断...一直判断到最后一个,选还是不选
这也就是01背包的名字的来源,0和1代表着选还是不选
所以状态转移方程就是//这里令f[i][j]是背包容量为j的情况下,第i的物品的最优解
f[i][j]=max(f[i-1][j],f[i-1][j-v[i])+w[i])
#include<iostream> using namespace std; const int N=1e3+10; int f[N][N],v[N],w[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; f[0][0]=0; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(j<v[i]) f[i][j]=f[i-1][j]; else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]); } } cout<<f[n][m]<<endl; return 0; }
转化到一维的情况:就是f[j]为背包容量为j下的最优解(为什么可以转化:题目意思很明确让求n个物品的最大价值,所以说又干嘛浪费空间去把每第几个物品的最优解求出来呢)
然后在不选的时候,反正不选也就不选了,背包容量没有任何改变,所以还是f[j],状态转移方程是:
f[j]=max(f[j],f[j-v[i]]+w[i])
#include<iostream> using namespace std; const int N=1e3+10; int f[N],v[N],w[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]<<endl; return 0; }
分组背包:
这个可能看起来和01背包最像了,(个人认为),并不是说代码啥的像,而是思想方法很像,完全就是选与不选的问题,这个像是01背包的拓展吧
问题就是把物品们分组了,但是这每一组只能选择一个,其他条件都一样
这个就是枚举每个组数,这下的描述方法就要改一下了,刚刚的01背包是枚举n个物品,看看第i个选还是不选。这个分组背包,因为每组只能选1一个,就枚举每个分组
这下的描述是:要么选第1个,要么选第2个,要么选第3个...要么选第n个,所以直接用n个物品,求最大值就是了,很简单
#include<iostream> using namespace std; const int N=110; int f[N],v[N],w[N]; int main(){ int n,m; cin>>n>>m; for(int i=0;i<n;i++) { int s; cin>>s; for(int j=0;j<s;j++) cin>>v[j]>>w[j]; for(int j=m;j>=0;j--) { for(int k=0;k<s;k++) { if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]); } } } cout<<f[m]<<endl; return 0; }
看代码,注意啊!!!这里的j是从大到小枚举体积(因为只能选择一次),for(int j=m;j>=0;j--)这里的j>=0,因为思维定势,我之前写成了j>=v[i],还想不明白....但其实,要弄清楚原理啊,
之前的>=v[i]是枚举i个物品的时候背包容量要大于等于物品体积,那个时候i枚举的就是物品,而这里不一样,这里的i枚举的是分组,而真正的决策是在k上面,就是选与不选的问题
没办法判断了,所以>=0枚举,不过在全部枚举的时候最后加了个判断条件么,这里的细节需要弄清楚!
多重背包:
这里的多重背包就加了个条件而已,就是某个物品最多可以选择s次,就这个,我喜欢把它拆成01背包来写嘻嘻嘻嘻
所以啊,枚举决策个数的时候,多加了个循环,这里要看数据范围了,如果很小就是可以三层for循环,很大就是简单粗暴的拆成01背包再优化(使用二进制)
小数据:
#include<iostream> using namespace std; const int N=110; int f[N][N],v[N],w[N],s[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i]; for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++)//因为可以重复选,所以从小到大枚举 { for(int k=0;k<=s[i]&&k*v[i]<=j;k++) { f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); } } } cout<<f[n][m]<<endl; return 0; }
大数据:
#include<iostream> using namespace std; const int N=1e5+10; int f[N],v[N],w[N]; int main(){ int n,m; cin>>n>>m; int t=0; for(int i=0;i<n;i++) { int a,b,c; cin>>a>>b>>c; int k=1; while(k<=c) { t++; v[t]=a*k;//t就是存数组的下标 w[t]=b*k;//k是二进制分堆 c-=k; k*=2; } if(c>0) { t++; v[t]=a*c; w[t]=b*c; } } n=t; for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]<<endl; return 0; }
在这里我想放个题,没啥为什么,就是心血来潮:
P1833 樱花
P1833 樱花 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
《爱与愁的故事第四弹·plant》第一章。
题目描述
爱与愁大神后院里种了 nn 棵樱花树,每棵都有美学值 C_i(0 \le C_i \le 200)Ci(0≤Ci≤200)。爱与愁大神在每天上学前都会来赏花。爱与愁大神可是生物学霸,他懂得如何欣赏樱花:一种樱花树看一遍过,一种樱花树最多看 A_i(0 \le A_i \le 100)Ai(0≤Ai≤100) 遍,一种樱花树可以看无数遍。但是看每棵樱花树都有一定的时间 T_i(0 \le T_i \le 100)Ti(0≤Ti≤100)。爱与愁大神离去上学的时间只剩下一小会儿了。求解看哪几棵樱花树能使美学值最高且爱与愁大神能准时(或提早)去上学。
输入格式
共 n+1n+1行:
第 11 行:现在时间 T_sTs(几时:几分),去上学的时间 T_eTe(几时:几分),爱与愁大神院子里有几棵樱花树 nn。这里的 T_sTs,T_eTe 格式为:hh:mm
,其中 0 \leq hh \leq 230≤hh≤23,0 \leq mm \leq 590≤mm≤59,且 hh,mm,nhh,mm,n 均为正整数。
第 22 行到第 n+1n+1 行,每行三个正整数:看完第 ii 棵树的耗费时间 T_iTi,第 ii 棵树的美学值 C_iCi,看第 ii 棵树的次数 P_iPi(P_i=0Pi=0 表示无数次,P_iPi 是其他数字表示最多可看的次数 P_iPi)。
输出格式
只有一个整数,表示最大美学值。
输入输出样例
6:50 7:00 3 2 1 0 3 3 1 4 5 4
11
说明/提示
100\%100% 数据:T_e-T_s \leq 1000Te−Ts≤1000(即开始时间距离结束时间不超过 10001000 分钟),n \leq 10000n≤10000。保证 T_e,T_sTe,Ts 为同一天内的时间。
样例解释:赏第一棵樱花树一次,赏第三棵樱花树 22 次。
这个题看一眼题目再看一眼数据范围:二进制优化的多重背包;可惜了,我是照着板子写的,得再熟练一下,然后时间转化就直接找个基准数算差值。哦对,当p==0的时候,可以看无限次,把当前的s变为1000就好了(1000是最大的)
#include<iostream> using namespace std; const int N=1e5+10; int w[N],v[N],s[N],f[N]; int main(){ int a,b,c,d,n,x,y,m,A,B,C; char ch1,ch2; cin>>a>>ch1>>b; cin>>c>>ch2>>d; cin>>n; int t=0; for(int i=1;i<=n;i++) { cin>>A>>B>>C; int k=1; if(C==0) C=1000; while(k<=C) { t++; v[t]=A*k; w[t]=B*k; C-=k; k*=2; } if(C>0) { t++; v[t]=A*C; w[t]=B*C; } } x=a*60+b; y=c*60+d; m=y-x; n=t; for(int i=1;i<=n;i++) { for(int j=m;j>=v[i];j--) { f[j]=max(f[j],f[j-v[i]]+w[i]); } } cout<<f[m]<<endl; return 0; }
题目描述
有一家奇怪的银行,每次只允许顾客存一定量的钱,具体规定如下:
1.存1元
2.存6元,36元,216元...6^k元
3.存9元,81元,729元...9^k元
现在假设你有A元想存入银行,至少要存几次呢?
输入
一个数 A
输出
一个数,最少要存的次数
样例输入 Copy
127
样例输出 Copy
4
提示
A<=100000
样例解释:
4次分别为:1,9,36,81元
这个我把数据存到数组里,强硬的想转化成01背包来写,不对的,因为某一个数可以被使用很多次,应该用无限次,应该用完全背包,但试了试还是不对...
然后就用dp来写,不过循环的条件我没设置好,感谢某位陈大佬的修改!!嘻嘻
#include<iostream> #include<cmath> using namespace std; const int N=1e5; int dp[N]; int main(){ int n; cin>>n; dp[0]=0; dp[1]=1; for(int i=2;i<=n;i++) { dp[i]=dp[i-1]+1; for(int x=6;x<=i;x=x*6) dp[i]=min(dp[i], dp[i-x]+1); for(int x=9;x<=i;x=x*9) dp[i]=min(dp[i], dp[i-x]+1); } cout<<dp[n]<<endl; } /* 127可以由126得到,可以由某个数加上9^k得到,可以由某个数加上6^k得到 令dp[i]是最少的次数 dp[i]=dp[i-1]+1; dp[i]=min(dp[i],dp[i-6^k]+1); dp[i]=min(dp[i],dp[i-9^k]+1); */
接下来就是完全背包了,这个虽然代码就是一个顺序,一个逆序,其他都没变,可是本质却差太多了,这个y总是用公式严格的推导出来的,在之前的博客也有写到,这里就不重复啦
#include<iostream> using namespace std; const int N=1010; int f[N],v[N],w[N]; int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>v[i]>>w[i]; for(int i=1;i<=n;i++) for(int j=v[i];j<=m;j++) f[j]=max(f[j],f[j-v[i]]+w[i]); cout<<f[m]<<endl; return 0; }
这些是我现在学的全部的背包问题了,如果以后再有拓展再来补充,咕噜咕噜
**************************************我是分割线,嘻嘻。
明天就开线性DP咯,开森呢!!数据结构两天没碰了...明天继续!加油!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具