动态规划整理,特详细!!!!!!!!!!
例题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]=1dp1[1]=arr[0];//dp1[0]=1;int ans = arr[0];//1 -2 0 3for(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]=3ans = 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] 如何确定了吧!
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 >= 1for(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;}
