线性DP&背包DP
具有线性规划特点的DP类型称为线性DP
这类DP一般是较为基础的蒟蒻不提简单二字
DP:
状态表示应满足三个特点:
1.最优化:满足最优子结构性质
(略微不同于贪心的“滚雪球”,DP算法不一定满足局部最优导致全局最优,但DP算法可以通过更新最优解实现全局最优)
2.无后效性:即当前问题的决策不受后续决策的影响,这就是无后效性
比方说大佬翻身成为蒟蒻
3.子问题的重复性:应用DP的前提是子问题的相似性,这使得我们可以递推出来实现最优解的关系式:状态转移方程
所以,状态转移方程是DP算法实现的关键
线性DP:
线性DP的状态转移沿着维度有方向增长
下面DP典例:
1.LIS(最长上升子序列):显然如果有
那么
与原以
那么通过如上分析便可得到状态转移方程:
例:设有由
1.合唱队形(1到i跑最长不下降,i到n跑最长不上升,然后两个值相加)
2.导弹拦截(第一问直接最长上升切掉,第二问1.贪心2.最长下降蒟蒻不知道咋证反正下降就完事了)
3.友好城市(最长上升)
4.打鼹鼠(特殊判断下的最长不“下降”)
最长(不)上升/下降子序列模板
#include <bits/stdc++.h>
using namespace std;
const int p=1e3+10;
int a[p],f[p],b[p],ans,l[p],n;//a:num,f:max,b:last,l:ans
void in(){
int i=1;
while(scanf("%d",&a[i])!=EOF){
f[i]=1;
i++;
}
n=i;
n--;
}
void work(){
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(a[i]>a[j]){//上升/不下降对应大于(等于),反之亦然
if(f[j]+1>f[i]){
f[i]=f[j]+1;
b[i]=j;
}
}
}
}
int flag;
for(int i=1;i<=n;i++){
if(f[i]>ans){
ans=f[i];
flag=i;
}
}
int r=flag,t=ans;//下标
while(r!=0){
l[t]=a[r];
r=b[r];
t--;
}
}
void out(){
printf("max=%d\n",ans);
for(int i=1;i<=ans;i++){
printf("%d ",l[i]);
}/*
for(int i=1;i<=n;i++){
cout<<f[i]<<" ";
}*/
}
int main(){
in();
work();
out();
return 0;
}
2.LCS(最长公共子序列)
这里我们用已处理过的部分和未处理过的部分相划分开
那么状态转移方程为:
当且仅当
背包DP:原本背包9讲变7讲,我再吞3讲也没事吧
首先对于所有的背包DP来说,它们的状态转移方程统一,为:
其中i表示第i种物品对应价值
注释:在上述状态转移方程中:
此状态下加上
与当前的最优状态比较,完成最大值的更新,最优状态的转移完成
1.0/1DP
N种物品,每种物品仅有1个,价值为C,价格为V,你所能付出的最大代价为M,
试求出不超过承受最大代价时所能收获的最大价值
套上述状态转移方程即可
例题
1.采药(纯纯一裸的0/1背包,没有任何变化)
2.开心的金明(注意价值需要自己求出来即预处理,然后裸的0/1背包即可)
Code
点击查看代码
void DP(){
for(int i=1;i<=n;i++){//从第1种物品开始枚举到第n种物品
for(int j=m;j>=v[i];j--){//0/1背包倒序查找防止出现某种物品重复使用
f[j]=max(f[j],f[j-v[i]]+c[i]);//状态转移方程
}
}
for(int i=1;i<=m;i++){//查找各种代价所得到的最优解
ans=max(ans,f[i]);//用最优解更新答案
}
cout<<ans;//输出答案
return;//结束,会了
}
2.完全背包
N种物品,每种无数个,价值C,价格V,最大承受代价M
仍问可承受范围内最大价值
还是一套状态转移方程,只不过循环结构适当变化
例题:
1.竞赛总分(纯纯的裸完全背包,直接套模板即可)
2.最小乘车费用(转化一下,以路程为代价,以车费为价值,这样就是一个完全背包求最小值问题)
Code
点击查看代码
void DP(){
for(int i=1;i<=n;i++){//从1到n遍历物品
for(int j=v[i];j<=m;j++){//不同于0/1,这里正序循环保证可以使用无限件物品,当j的值从Ma变到Ma+v【i】时还可以使用i物品
f[j]=max(f[j],f[j-v[i]]+c[j]);//状态转移更新
}
}
for(int i=1;i<=m;i++){//遍历各种代价下的最优解
ans=max(ans,f[i]);//更新答案
}
cout<<ans;//输出结果
return;//结束
}
3.多重背包
N种物品,每种P个,价值C,价格V,最大承受M
例题:
1.庆功会(裸多重,套模板)
暴力直接易想可打,无非就是种类(阶段)-》重量(状态)-》数量(决策)过程
然后T掉,emo一整天优化
首先是二进制优化,把物品数以2的k次幂(k初始值为0)分割下来,然后k++的过程
该流程用程序表达为
void pre(int n) {int cnt=1,k=0; while(n>=cnt){ n-=cnt; k++; cnt*=2;} }
,
再把分割下的这些物品当做一个整体的物品,最后跑0/1背包即可
二进制优化
void dp(){
int base=1;
int t=p[i];
while(t){
t-=base;
int tv=base*w[i],tw=base*c[i];
for(int j=m;j>=tv;j--){
f[j]=max(f[j],f[j-tv]+tw);
}
base*=2;
base=min(base,t);
}
}
也有
单调队列优化算法
#include <bits/stdc++.h>//单调队列维护多重背包
using namespace std;
int n,m,ans;
int v[501],w[501],c[501],f[6000];
int q[10000];
int calc(int i,int u,int k){
return f[u+k*v[i]]-k*w[i];
}
int main(){
cin>>n>>m;
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&v[i],&w[i],&c[i]);
for(int u=0;u<v[i];u++){
int l=1,r=0;
int maxp=(m-u)/v[i];
for(int k=maxp-1;k>=max(maxp-c[i],0);k--){
while(l<=r&&calc(i,u,q[r])<=calc(i,u,k))r--;
q[++r]=k;
}
for(int p=maxp;p>=0;p--){
while(l<=r&&q[l]>p-1)l++;
if(l<=r)
f[u+p*v[i]]=max(f[u+p*v[i]],calc(i,u,q[l])+p*w[i]);
if(p-c[i]-1>=0){
while(l<=r&&calc(i,u,q[r])<=calc(i,u,p-c[i]-1))r--;
q[++r]=p-c[i]-1;
}
}
}
}
for(int i=1;i<=m;i++){
ans=max(ans,f[i]);
}
cout<<ans;
return 0;
}
1.2.3综合的混合背包
1.逃亡的准备(优化多重及0/1,二进制优化即可)
2.模板混合(数据很弱,甚至多重不用二进制)
1/2/3 延伸:二维背包:毫无疑问状态多一维即可
然而这玩意没法压维省空间。。。
4.分组背包
Code
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--)
for(int k=1;k<=c[i];k++)
for(int k=1;k<=c[i];k++)
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+c[i][k]);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具