线性DP&背包DP

具有线性规划特点的DP类型称为线性DP
这类DP一般是较为基础的蒟蒻不提简单二字
DP:
状态表示应满足三个特点:
1.最优化:满足最优子结构性质
(略微不同于贪心的“滚雪球”,DP算法不一定满足局部最优导致全局最优,但DP算法可以通过更新最优解实现全局最优)
2.无后效性:即当前问题的决策不受后续决策的影响,这就是无后效性
比方说大佬翻身成为蒟蒻
3.子问题的重复性:应用DP的前提是子问题的相似性,这使得我们可以递推出来实现最优解的关系式:状态转移方程
所以,状态转移方程是DP算法实现的关键
线性DP:
线性DP的状态转移沿着维度有方向增长
下面DP典例:
1.LIS(最长上升子序列):显然如果有i,j,i>j&Ai>Aj
那么Ai便可以添加到以Aj为结尾的上升子序列中,
与原以Ai结尾的上升子序列相比可得长度更长的更优,也就是状态更新
那么通过如上分析便可得到状态转移方程:
fi=max{fj+1},Ai>Aj,j[1,i),特殊的,f0=0
例:设有由n个不相同的整数组成的数列,记为:b(1)b(2)b(n),ij,b(i)b(j),若存在i1<i2<i3<<ie,b(i1)<b(i2)<<b(ie)则称为长度为e的不下降序列。程序要求,当原数列出之后,求出最长的上升序列。
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(最长公共子序列)
这里我们用已处理过的部分和未处理过的部分相划分开
那么状态转移方程为:
fi,j=max(fi1,j,fi,j1,fi1,j1+1)
当且仅当Ai=bj的时候转移fi1,j1+1可用

背包DP:原本背包9讲变7讲,我再吞3讲也没事吧
首先对于所有的背包DP来说,它们的状态转移方程统一,为:
fj=max(fjwi+vi)
其中i表示第i种物品对应价值vi,价格wij表示包内重量为j时所带来的最大价值为fj(上述价格与重量均表示为“代价”)
注释:在上述状态转移方程中:fjwi毫无疑问是在包内质量位于jwi时所获得最大值的状态,
此状态下加上vi表示假设如果在此状态下加上vi的值(显然wi对前一部分无影响,仍是理想下的最优状态),
与当前的最优状态比较,完成最大值的更新,最优状态的转移完成
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);
	}
}

也有O(MN)算法,即单调队列优化DP的写法

单调队列优化算法
#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]);
posted @   2K22  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示