例题1

组成平方数

题目描述

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

输入输出描述

输入描述

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

输出描述

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

输入输出样例

输入格式#1:

4

输出格式#1:

1

输入格式#2:

10

输出格式#2:

2

输入格式#3:

19

输出格式#3:

3

输入格式#4:

245

输出格式#4:

2

输入格式#5:

3281

输出格式#5:

2

输入格式#6:

234

输出格式#6:

2

输入格式#7:

666

输出格式#7:

2

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int main()
{
   
	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:

4
1 -2 0 3

输出格式#1:

4

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


输入格式#2:

4
1 -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 main()
{
   
	int len;
	cin>>len;
	int arr[len];
	for(int i=0;i<len;i++)
	{
   
	    cin>>arr[i];
	}
	//1 -2 0 3
	int f[len];
	 int g[len];
	 f[0] = arr[0];
	 g[0] = -200001;
	//-3 -4 -5 2
	for(int i=1;i<len;i++)
	{
   
	    //i=1 f[1]=max(-1,-2)=-1 g[1]=1
	    //i=2 f[2]=max(-1,0)=0 g[2]=1
	    //i=3 f[3]=max(3,3)=3 g[3]=4
	    f[i] = max(f[i-1]+arr[i],arr[i]);//其实就是f(i-1)是否<0
	    g[i] = max(g[i-1]+arr[i],f[i-1]);//取出g[i-1]加新数,和f[i-1]中最大值
	}
	int res = -2000000;
	for(int i=0;i<len;i++){
   
	    res = max(res,max(f[i],g[i]));//取出f[i],g[i]最大值
	}
	cout<<res;
}

例题3

最大的连续子序列

题目描述

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

输入输出描述

输入描述

共两行:

第一行,一个整形数据n,表示数据个数,

第二行有n个数据表示数据mum s。

输出描述

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

输入输出样例

输入格式#1:

5
1 2 3 4 5

输出格式#1:

120

输入格式#2:

5
1 -2 3 -4 5

输出格式#2:

120

输入格式#3:

1
2

输出格式#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=n;j>=0;j--)
        {
   
            if(j-w[i]>=0)
            {
   
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
    }
    cout<<dp[n];
}

感谢<mark>Albert.Jw</mark>发现错误

关键是内循环中为什么是逆序的呢,因为要记算当前状态的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]是前一状态的!

过程由于太多,请见下网址
https://dwz.cn/6hyKoSTA

例题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;
}

过程由于太多,请见下网址
https://dwz.cn/GeY4UriQ

例题6

最大子段和

题目描述

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

输入输出描述

输入描述

第一行是一个正整数N,表示了序列的长度。

第二行包含N个绝对值不大于10000的整数Ai​,描述了这段序列。

输出描述

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

输入输出样例

输入格式#1:

7
2 -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:

5
3 2 1 4 5
1 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;
}

例题8

最长上升子序列

题目描述:

给定一个长度为 N 的数列,求它数值单调递增的子序列长度最大为多少。即已知有数列 A , A=A1,A2…An ,求 A的任意子序列
B ( B=Ak1,Ak2…Akp ),使 B 满足 k1<k2<…<kp且 Ak1<Ak2<…<Akp 。现求 p
的最大值。

输入格式

共二行。

第一行是一个整数N

第二行有n个整数

输出格式

一个整数,最长上升子序列长度

输入输出样例

输入

8
186 186 150 200 160 130 197 220

输出

4

#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int n,a[105],dp[105],ans;
int main()
{
   
    cin>>n; 
    for(int i=0;i<n;i++) 
    {
   
        cin>>a[i];
    }
    for(int i=0;i<n;i++) 
    {
   
        dp[i]=1;
        for(int j=0;j<n;j++) 
        {
   
            if(a[i]>a[j]) 
            {
   
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
    }
    int maxx=0;
    for(int i=0;i<n;i++)
    {
   
        maxx=max(maxx,dp[i]);
    }
    cout<<maxx;
    return 0;
}