vlambda博客
学习文章列表

动态规划整理,特详细!!!!!!!!!!

例题1

组成平方数

题目描述

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于n。你需要让组成和的完全平方数的个数最少。


输入输出描述

输入描述

一个整数n,表示要组成的数字

输出描述

一个正数表示最少需要几个完全平方数才能组成


输入输出样例

输入格式#1:

4

输出格式#1:

1

输入格式#2:

10

输出格式#2:

2

输入格式#3:

19

输出格式#3:

3



代码


#include<iostream>#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>using namespace std;int main(){ int a[100]; for(int i=0;i<100;i++) { a[i]=i*i; } int n; cin>>n; int dp[n+1];//从一开始 dp[0]=0; for(int i=1;i<=n;i++) { dp[i]=i; for(int j=1;i>=j*j;j++) { //见下图 dp[i]=min(dp[i],dp[i-j*j]+1); } } cout<<dp[n];}

动态规划整理,特详细!!!!!!!!!!

例题2

删除一次得到子数组最大和

题目描述

给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。
换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。
注意,删除一个元素后,子数组 不能为空。


输入输出描述

输入描述

一个整数数组arr


输出描述

最大和


输入输出样例


输入格式#1:

41 -2 0 3

输出格式#1:

4

解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。



输入格式#2:

41 -2 -2 3

输出格式#2:

3

解释:我们直接选出 [3],这就是最大和。



输入格式#3:

4-1 -1 -1 -1

输出格式#3:

-1

最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。



提示:

1 <= arr.length <= 10^5-10^4 <= arr[i] <= 10^4
#include<iostream>
#include<cstdio>#include<algorithm>#include<cmath>#include<cstring>using namespace std;int arr[1000];int main(){ int n; cin>>n;
for(int i=0;i<n;i++){ cin>>arr[i]; } int dp0[n],dp1[n]; memset(dp0,0,sizeof(dp0)); memset(dp1,0,sizeof(dp1)); dp0[0]=arr[0];//dp0[0]=1 dp1[1]=arr[0];//dp1[0]=1; int ans = arr[0]; //1 -2 0 3 for(int i=1;i<n;i++){ // 是添加上这个值的大,还是删除当前i值后包含i-1的值的区间大 dp0[i] = max(dp0[i - 1] + arr[i],dp0[i - 1]); //dp0[1]=1 dp0[2]=1 dp0[3]=4 旧区间大点,还是新区间大点,这时候任何数字都没被删除,必须包含当前i值没有删除任何值 dp1[i] = max(dp1[i - 1] + arr[i], arr[i]); //dp1[1]=-1 dp1[2]=0 dp1[3]=3 ans = max(ans,max(dp0[i],dp1[i])); } cout<<ans;}


例题3

最大的连续子序列

题目描述

给定一个整形数组nums,找出一个序列中乘积最大的连续子序列该序列至少包含一个数。


输入输出描述

输入描述

共两行:
第一行,一个整形数据n,表示数据个数,第二行有n个数据表示数据mum s。

输出描述

输出找到最大的连续子序列

输入输出样例

输入格式#1:

51 2 3 4 5

输出格式#1:

120

输入格式#2:

51 -2 3 -4 5

输出格式#2:

120

输入格式#3:

12

输出格式#3:

2
#include<bits/stdc++.h>
using namespace std;int main(){ int n; cin>>n; int a[n],b[n],c[n]; for(int i=0;i<n;i++) { cin>>a[i]; } int max1=b[0]=c[0]=a[0]; for(int i=1;i<n;i++) { //最大值乘以负数有可能变为最小值,最小值乘以负数有可能变为最大值 b[i]=max(max(a[i]*b[i-1],a[i]*c[i-1]),a[i]);//在不断的计算最大值 c[i]=min(min(a[i]*b[i-1],a[i]*c[i-1]),a[i]);//在不断的计算最小值 max1=max(max(b[i],c[i]),max1); } cout<<max1; return 0;}



例题4

普通背包

#include<bits/stdc++.h>using namespace std;int main(){int n,m; cin>>n>>m; int w[m],v[m],dp[n+1]; for(int i=0;i<m;i++) { cin>>w[i]>>v[i]; } memset(dp, 0, sizeof(dp)); for(int i=0;i<m;i++) { for(int j=0;j<=n;j++) { if(j-w[i]>=0) { dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } } cout<<dp[n];}



关键是内循环中为什么是逆序的呢,因为要记算当前状态的dp[j],需要的是前一状态的dp[j](即dp [j-1]),而逆序循环时前面的一定就是前一状态的dp[j],可以直接拿来用,而正序循环之所以不可以,是因为当你计算完前面的dp[j]时,dp[j-1]存的就不是i-1时刻的值了,而你后面还要用dp[j-1]。当内循环是逆序时,就可以保证后一个dp[j]和dp[j-w[i]]+v[i]是前一状态的!


过程由于太多,请见下二维码

动态规划整理,特详细!!!!!!!!!!

例题5

完全背包

#include<cstdio>#include<algorithm>using namespace std;int w[300],c[300],f[300010];int V,n;int main(){ scanf("%d%d",&V,&n); for(int i=1; i<=n; i++) { scanf("%d%d",&w[i],&c[i]); } for(int i=1; i<=n; i++) { for(int j=w[i]; j<=V; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序 { f[j]=max(f[j],f[j-w[i]]+c[i]); } } printf("max=%d\n",f[V]); return 0;}



过程由于太多,请见下二维码

动态规划整理,特详细!!!!!!!!!!


例题6

最大子段和

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。


输入输出描述

输入描述

第一行是一个正整数N,表示了序列的长度。
第二行包含N个绝对值不大于10000的整数Ai,描述了这段序列。


输出描述

一个整数,为最大的子段和是多少。子段的最小长度为1。


输入输出样例

输入格式#1:

72 -4 3 -1 2 -4 3

输出格式#1:

4

说明/提示

【样例说明】2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,−1,2.
【数据规模与约定】对于40%的数据,有N ≤ 2000对于100%的数据,有N ≤ 200000
#include<bits/stdc++.h>using namespace std;int n[200001],p,ans[200001]={0};//数组定义在外面int main(){ int sum=-9999999;//最小值 cin>>p;//个数 for(int i=1;i<=p;i++) { cin>>n[i]; ans[i]=max(ans[i-1]+n[i],n[i]);//等于前一个最大的字段加上这个数和这个数的最大值,不是顺着前一个,就是自己起一个头,看看那个的 sum=max(sum,ans[i]);//记录最大的 } cout<<sum;//输出 return 0;}

动态规划整理,特详细!!!!!!!!!!

例题7

组成平方数

题目描述

给出1-n的两个排列P1和P2,求它们的最长公共子序列。

输入输出描述

输入描述

第一行是一个数n,
接下来两行,每行为n个数,为自然数1-n的一个排列。


输出描述

一个数,即最长公共子序列的长度


输入输出样例

输入格式#1:

53 2 1 4 51 2 3 4 5

输出格式#1:

3

说明/提示

【数据规模】对于50%的数据,n≤1000对于100%的数据,n≤100000


别的方法就不进行赘述了,首先根据两个字符串的长度m,n生成一个(m + 1)x (n + 1)的数组dp,并且令dp[m][0] = 0,dp[0][n] = 0。为什么这么做呢,这里来解释一下:

动态规划是以空间换时间的利器吧,有了上个状态,经过我们的状态转移方程就可以推的下一阶段的状态。在这儿,每次操作的状态其实就是dp[i][j]的值。

动态规划整理,特详细!!!!!!!!!!

2. 就这样,我手动的全部填完了。

然后呢,我们分析一下有没有什么规律?如上图所示,我们已知结果为abcf,怎么得到的呢?或者说能不能用某个规律来说明我是怎么填好每个空的?(当然我们的大脑比较厉害,反正会填,不会的估计题都没看明白或者出题的真的是非人类了)。


结论:当两个子串逐字符进行比较的时候,若发现了相同的字符,则此刻的dp[i][j] = dp[i - 1][j - 1] + 1。除此之外,dp[i][j]就看它上面的值和左边的值哪个大了,把大的值赋给它。


注意!我们虽然写的是dp[i][j],但实际比较的是String1[i-1]和String2[j-1]。为啥呢?看图吧,当我填dp[i][j] = 1的时候,是因为我发现了String1[0] = String2[0] = "a"的时候吧?而此时的dp[i][j]中的i和j都等于1。现在多少明白为什么定义(m + 1)x(n + 1)了吧?为什么预先设定了哪些0?可以理解为两个字符串,其中一个若为空字符串,还有个锤子的公共子序列啊~

动态规划整理,特详细!!!!!!!!!!

3.如上图所示,我们知道dp[i][j] 如何确定了吧!


#include <string>#include <algorithm>#include<iostream>using namespace std;string s1, s2;int main() { cin >> s1; cin >> s2; int m = s1.size(); int n = s2.size(); int dp[n+1][m+1]; for(int i = 0; i < n+1; i++) { dp[i][0] = 0; } for(int i = 0; i < m+1; i++) { dp[0][i] = 0; } // 根据设定的规则一次填入dp[i][j],其中i, j >= 1 for(int i = 1; i < n+1; i++) { for(int j = 1; j < m+1; j++) { if(s1[j-1] == s2[i-1]) { dp[i][j] = dp[i-1][j-1] + 1; } else if(dp[i][j-1] > dp[i-1][j]) { dp[i][j] = dp[i][j-1]; } else { dp[i][j] = dp[i-1][j]; } } } cout << dp[n][m] << endl; return 0;}

动态规划整理,特详细!!!!!!!!!!

动态规划整理,特详细!!!!!!!!!!