动态规划一
复健
动态规划(一)线性
数字三角形模型
此类题目的一般描述为给定一个行的三角矩阵,从第行列出发,每次只能向下或者右下移动,到达底层后求某一属性的最优值
一般的解法都是设状态为,表示到达时的属性最优值
摘花生
https://www.acwing.com/problem/content/1017/
非常基础的模型,直接转移即可,不过注意每次询问的时候都需先把数组清零
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 110
using namespace std;
int dp[maxn][maxn];
int f[maxn][maxn];
int dir[2][2]={{0,1},{1,0}};
int main()
{
int t,m,n;
cin>>t;
while(t--)
{
memset(dp,0,sizeof(dp));
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>f[i][j];
}
}
dp[1][1]=f[1][1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<2;k++)
{
if(i+dir[k][0]>n||j+dir[k][1]>m) continue;
dp[i+dir[k][0]][j+dir[k][1]]=
max(dp[i+dir[k][0]][j+dir[k][1]],dp[i][j]+f[i+dir[k][0]][j+dir[k][1]]);
}
}
}
printf("%d\n",dp[n][m]);
}
return 0;
}
最低通行费用
题目描述:
一个商人穿过一个的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间个小方格,都要花费个单位时间。
商人必须在个单位时间穿越出去。
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即只能向上下左右四个方向移动且不能离开网格)。
输入格式:
第一行是一个整数,表示正方形的宽度。
后面行,每行个不大于的正整数,为网格上每个小方格的费用。
输出格式:
输出一个整数,表示至少需要的费用。
数据范围:
注意到我们需要在的时间内出去,实际也就是意味着我们只能往下或者往右跑,与上一题思路相同,设状态为
然后代码也上一题相似
不过一开始做的时候我并没有想到这一性质,于是采用了一个三维数组维护最小费用表示穿过时间为的最小费用,然后这个算法的时间复杂度应该是的,我认为是可以通过的,但是因为我没报算法提高班(T^T),无法验证对错与时间,不过这个代码也是通过了样例的,放在此处仅供大家参考
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 110
#define tox i+dir[t][0]
#define toy j+dir[t][1]
using namespace std;
int f[maxn][maxn];
int dp[maxn][maxn][maxn];
int dir[4][4]={{0,-1},{0,1},{-1,0},{1,0}};
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>f[i][j];
}
}
memset(dp,0x3f,sizeof(dp));
dp[1][1][1]=f[1][1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<2*n-1;k++)
{
for(int t=0;t<4;t++)
{
if(tox<=0||tox>n||toy<=0||toy>n) continue;
dp[tox][toy][k+1]=min(dp[tox][toy][k+1],dp[i][j][k]+f[tox][toy]);
//printf("i:%d j:%d dp:%d\n",tox,toy,dp[tox][toy][k+1]);
}
}
}
}
int Min=0x3f3f3f3f;
for(int i=1;i<=2*n-1;i++) Min=min(Min,dp[n][n][i]);
printf("%d\n",Min);
return 0;
}
/*
5
1 4 6 8 10
2 5 7 15 17
6 8 9 18 20
10 11 12 19 21
20 23 25 29 33
*/
最长上升子序列模型
关于最长上升子序列,最暴力的求法是的复杂度
表示以第个数字结尾的最长上升子序列长度
#include<iostream>
#include<cstdio>
#define maxn 2005
using namespace std;
int a[maxn];
int dp[maxn];
int main()
{
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}
在寻找序号小于且比小的数字时,我们可以在此处进行优化,采用栈和二分的方法,将这一部分从优化为的复杂度
我们维护一个栈,栈中的元素是可以更新之后的元素的值的最优的元素,每当我们枚举到一个时,如果大于栈顶元素,那么我们就把这个元素入栈,并用原来栈顶的元素去更新这个元素的值,而如果其小于等于栈顶元素,那么我们就采用二分的方法(注意二分的是栈里的元素)找到栈中最小的比它大的数,然后将其出栈,用这个元素去替换该元素。
我们在替换之后栈的长度并没有改变,但是在此处的数的潜力却变大了
栈的长度就是最长公共子序列
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 10010
#define inf 0x3f3f3f3f
using namespace std;
int a[maxn];
int main()
{
int n;
cin>>n;
int l,r,tot=0;
a[tot]=-inf;
while(n--)
{
int u;
cin>>u;
if(u>a[tot]) a[++tot]=u;
else
{
l=1,r=tot;
while(l<=r)
{
int mid=l+r>>1;
if(u>a[mid]) l=mid+1;
else r=mid-1;
}
a[l]=u;
}
}
printf("%d\n",tot);
return 0;
}
(1)合唱队形
https://www.luogu.com.cn/problem/P1091
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 10010
#define inf 0x3f3f3f3
using namespace std;
int w[maxn],a[maxn],up[maxn],down[maxn];
int main()
{
int n;
int tot=0;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
a[0]=-inf;
for(int i=1;i<=n;i++)
{
if(w[i]>a[tot]) a[++tot]=w[i];
else
{
int l=1,r=tot;
while(l<=r)
{
int mid=(l+r)>>1;
if(w[i]>a[mid]) l=mid+1;
else r=mid-1;
}
a[l]=w[i];
}
up[i]=tot;
}
tot=0;
memset(a,0,sizeof(a));
a[tot]=-inf;
for(int i=n;i>=1;i--)
{
if(w[i]>a[tot]) a[++tot]=w[i];
else
{
int l=1,r=tot;
while(l<=r)
{
int mid=(l+r)>>1;
if(w[i]>a[mid]) l=mid+1;
else r=mid-1;
}
a[l]=w[i];
}
down[i]=tot;
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,up[i]+down[i]-1);
printf("%d\n",n-ans);
return 0;
}
也就是正倒个进行一次最长不上升序列,最后得出满足条件的合唱队队形长度,最后的答案就是总人数减去队形的人数
这道题由于题目的数据范围限制,用用法会简便许多(此处我只是为了锻炼一下单调队列写法的熟练度故用此方法)
(2)木棍加工
https://www.luogu.com.cn/problem/P1233
这道题用到了定理(偏序集分解定理):对于任意有限偏序集,其最长链的元素数目必等于其最小反链划分中反链的数目
偏序关系:设是一个集合,若上的二元关系满足以下三个条件则称其为上的偏序关系
自反性:,
反对称性:,若且,则
传递性:,若且,则
偏序集:具有偏序关系的集合称为偏序集
链:链是一种偏序集,其任意两个元素具有不具有可比性
反链:反链是一种偏序集,其任意两个元素不具有可比性
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define maxn 5010
using namespace std;
int dp[maxn];
struct Node
{
int h,w;
bool operator < (const Node &rhs){
if(h==rhs.h) return w>rhs.w;
else return h>rhs.h;
}
}node[maxn];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>node[i].h>>node[i].w;
int ans=0;
sort(node+1,node+n+1);
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(node[i].w>node[j].w) dp[i]=max(dp[i],dp[j]+1);
}
}
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}
最长公共子序列
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 3010
using namespace std;
int p[maxn][maxn];//p[i][j]记录转移到[i][j]时的前驱数组,1表示从左上方[i-1][j-1],2表示
//左方[i][j-1],3表示从上方转移[i-1][j]
int f[maxn][maxn];
int main()
{
int n,m;
string a,b;
cin>>a>>b;
n=a.length();m=b.length();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])
{
f[i][j]=f[i-1][j-1]+1;
p[i][j]=1;
}
else
{
if(f[i][j-1]>f[i-1][j])
{
f[i][j]=f[i][j-1];
p[i][j]=2;
}
else
{
f[i][j]=f[i-1][j];
p[i][j]=3;
}
}
}
}
int i=m,j=n,k=f[n][m];
string s;
while(i>0&&j>0)
{
if(p[i][j]==1)
{
s[k--]=a[i-1];
i--;j--;
}
else if(p[i][j]==2) j--;
else i--;
}
printf("%d\n",f[n][m]);
for(int i=1;i<=n;i++) printf("%c",s[i]);
return 0;
}
最长公共子串
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 2010
using namespace std;
int f[maxn][maxn];
string a,b;
int ans=0;
int main()
{
cin>>a>>b;
int n=a.length(),m=b.length();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=0;
ans=max(ans,f[i][j]);
}
}
printf("%d\n",ans);
return 0;
}
编辑距离
这是普通的模板,时间复杂度为
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 2010
using namespace std;
int f[maxn][maxn];
string a,b;
int ans=0;
int main()
{
cin>>a>>b;
int n=a.length(),m=b.length();
for(int i=1;i<=n;i++)
{
f[i][0]=i;
for(int j=1;j<=m;j++)
{
f[0][j]=j;
if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1];
else
{
f[i][j]=min(f[i-1][j-1],min(f[i][j-1],f[i-1][j]))+1;
}
}
}
printf("%d\n",f[n][m]);
return 0;
}
我们呢可以采用滚动数组优化,使得其空间复杂度降为
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 2010
using namespace std;
int f[maxn];
string a,b;
int ans=0;
int main()
{
cin>>a>>b;
int t1,t2;
int n=a.length(),m=b.length();
for(int j=1;j<=m;j++) f[j]=j;
for(int i=1;i<=n;i++)
{
t1=f[0]++;//t1等价于f[i-1][0]
for(int j=1;j<=m;j++)
{
t2=f[j];//t2存储更新前的f[j]
if(a[i-1]==b[j-1]) f[j]=t1;
else f[j]=min(t1,min(f[j-1],f[j]))+1;
t1=t2;//t1等价于f[i-1][j-1]
}
}
printf("%d\n",f[m]);
return 0;
}
例题:
大盗阿福
表示从第家到第家的店铺进行偷盗,上一家不抢/抢从而得到的最大现金数
则其转移是由从而得来的
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100010
using namespace std;
int f[maxn][2];
int w[maxn];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=1;i<=n;i++)
{
f[i][0]=max(f[i][0],max(f[i-1][1],f[i-1][0]));
f[i][1]=max(f[i][1],f[i-1][0]+w[i]);
}
printf("%d\n",max(f[n][0],f[n][1]));
}
return 0;
}
股票买卖
https://www.acwing.com/problem/content/1057/
表示到第天手上没有/有股票的到的最大利润
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100010
using namespace std;
int f[maxn][2];
int w[maxn];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
f[1][0]=0,f[1][1]=-1*w[1];
for(int i=2;i<=n;i++)
{
f[i][0]=max(f[i-1][0],f[i-1][1]+w[i]);
f[i][1]=max(f[i-1][1],f[i-1][0]-w[i]);
}
printf("%d\n",max(f[n][0],f[n][1]));//实际上最大的一定是f[n][0]
return 0;
}
股票买卖笔交易
https://www.acwing.com/problem/content/1059/
表示到第笔交易有无股票同时完成了笔交易的最大利润值
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100010
using namespace std;
int f[maxn][2][110];
int w[maxn];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>w[i];
memset(f,-0x3f,sizeof(f));
int ans=0;
f[1][0][0]=0,f[1][1][0]=-w[1];
for(int i=2;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
f[i][0][j]=f[i-1][0][j];
if(j>=1) f[i][0][j]=max(f[i][0][j],f[i-1][1][j-1]+w[i]);
f[i][1][j]=max(f[i-1][1][j],f[i-1][0][j]-w[i]);
ans=max(ans,max(f[i][0][j],f[i][1][j]));
}
}
printf("%d\n",ans);
return 0;
}
股票交易(含冷冻期)
表示手中有票的最大利润,表示第天手中无票第一天的最大利润,表示手中无票第二天及以后的最大利润
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 100010
using namespace std;
int f[maxn][3];
int w[maxn];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
f[1][0]=f[1][2]=0,f[1][1]=-w[1];
for(int i=2;i<=n;i++)
{
f[i][0]=f[i-1][1]+w[i];
f[i][1]=max(f[i-1][1],f[i-1][2]-w[i]);
f[i][2]=max(f[i-1][2],f[i-1][0]);
}
printf("%d\n",max(f[n][0],f[n][2]));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】