信息学奥赛第二十节 —— 动态规划2(最大连续部分和 + 最长不下降子序列LIS)

最大连续部分和(原题链接)

题目描述

有n个整数(1≤n≤100),排成一排,例如
n=7
-2 13 12 9 14 -10 2 (7个整数)
其最大的部分和为 48 (即 13+12+9+14)

输入

第一行一个整数 n
第二行n个整数(-100≤xi≤100)
数之间有一个空格;其中xi有正数

输出

一个整数(即最大的连续的部分和)

样例输入

7
-2 13 12 9 14 -10 2

样例输出

48

提示

【来源】 2014江苏省青少年信息学奥林匹克竞赛复赛

解题思路1暴力法:本题n∈[1,100],故可以暴力枚举所有的连续和,取其最大值。时间复杂度:O(n ^ 2)
AC代码1

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110;
int a[N];//存储数据

int main()
{
    int n,sum,ans = 0;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i];
    
    for (int i = 1;i <= n;i++)//i用来枚举第一个数的位置
    {
        sum = 0;
        for (int j = i;j <= n;j++)//从第i个位置开始,枚举长度
        {
            sum += a[j];
            ans = max(ans,sum);
        }
    }
    cout << ans << endl;
    return 0;
}

解题思路2动态规划:设dp[i]表示以第i个位置的数结尾的最大连续部分和。不难发现,dp[1] = -2dp[2] = 11dp[3] = 25… …其状态转移方程为:

dp[i] = max(a[i],dp[i - 1] + a[i]);

AC代码2

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110;
int a[N];//存储数据
int dp[N];//dp[i]表示以第i个位置的数为结尾的最大连续和

int main()
{
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i];
    
    dp[1] = -2;
    int ans = -2;
    
    for (int i = 2;i <= n;i++)
    {
        //dp[i]的两种情况:1、选a[i]:dp[i - 1] + a[i] 2、不选dp[i - 1]得a[i],取较大值
        dp[i] = max(a[i],dp[i - 1] + a[i]);
        ans = max(ans,dp[i]);
    }
    
    cout << ans << endl;
    // for (int i = 1;i <= n;i++) cout << dp[i] << " ";
    return 0;
}
最长不下降子序列LIS(原题链接)

题目描述

设有由n个不相同的整数组成的数列,记为: a(1)、a(2)、……、a(n)且a(i)<>a(j) (i<>j)。例如3,18,7,14,10,12,23,41,16,24。若存在i1<i2<i3<… < ie且有a(i1)<a(i2)<… <a(ie)则称为长度为e的不下降序列。如上例中3,18,23,24就是一个长度为4的不下降序列,同时也有3,7,10,12,16,24长度为6的不下降序列。程序要求,当原数列给出之后,求出最长的不下降序列。

输入

第一行为n,表示n个数
第二行n个整数,数值之间用一个空格分隔

输出

最长不下降子序列的长度

样例输入

3
1 2 3

样例输出

3

提示

数据范围
1≤N≤10000,
−109≤数列中的数≤109

解题思路LIS —— 动态规划的经典模型:最长上升子序列。给定一个无序的整数数组,找出其中最长上升子序列的长度。
对于如下序列:

【5 7 1 9 4 6 2 8 3】

其最长上升子序列为

【1 4 6 8】,长度为4

我们从左向右逐段扫描,不难发现:
以5为结尾的LIS:5
以7为结尾的LIS:5 7
以1为结尾的LIS:1 (1的前面没有比1更小的数字)
以9为结尾的LIS:5 7 9
以4为结尾的LIS:1 4 (4的前面只有1比它小)
以6为结尾的LIS:1 4 6
以2为结尾的LIS:1 2
以8为结尾的LIS:1 4 6 8
以3为结尾的LIS:1 2 3 (3的前面只有1、2比它小)

dp[i]表示以a[i]为结尾的最长不下降子序列的长度。

  • 初始时,将dp数组初始化为1。定义两个指针为i、ji指针指向a[1]j指针指向a[0],由于a[0] < a[1],所以dp[1] = 1
    在这里插入图片描述
  • 此时i指针指向a[2]j指针遍历a[2]之前的数字,由于a[1] < a[2],所以将dp[2]更新为dp[1] + 1,即dp[2] = 2
    在这里插入图片描述
  • 下一步i指针指向a[3]j指针遍历a[1]、a[2],发现没有小于a[3]的数字,所以不更新dp数组
  • 再下一步i指针指向a[4]j指针遍历a[1]、a[2]、a[3],发现a[1] < a[4],所以更新dp[4] = dp[1] +1 = 2,之后又发现a[2] < a[4],所以更新dp[4] = dp[2] +1 = 3,最终如下图。
    在这里插入图片描述
  • 以此类推,得到最终的dp数组如下
    在这里插入图片描述
    总结:如果a[i] > a[j],且dp[j] + 1 > dp[i],则更新dp[i] = dp[j] + 1。将第二个条件并入表达式之后,则可以推导出状态转移方程为:
if (a[i] > a[j]) dp[i] = max(dp[i],dp[j] + 1)
思考
  • 算法的时间复杂度?答:O(n ^ 2)
  • dp[8]由谁更新得到?答:dp[6] + 1 = 3 + 1 = dp[8]
  • 如果设dp[i]为前i个数的最长上升子序列的长度,可以吗?答:不可以。由上表得知,dp[4] = 3,即前4个数的最长上升子序列为5、7、9,它的长度为3。如果按照这种设法,前5个数的最长上升子序列还是5、7、9。

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1e4 + 10;
int a[N];//存储数据
int dp[N];//dp[i]表示以第i个位置的数为结尾的最长不下降子序列的长度
int ans = 0;

int main()
{
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) scanf("%d",&a[i]);
  
    for (int i = 1;i <= n;i++) dp[i] = 1;//初始化 
    
    for (int i = 1;i <= n;i++)
    {
        for (int j = 1;j < i;j++)
        {
            if (a[j] < a[i])
                dp[i] = max(dp[j] + 1,dp[i]);
        }
        
        ans = max(dp[i],ans);
    }
    // for (int i = 1;i <= n;i++) cout << dp[i] << " ";
    cout << ans << endl;
    return 0;
}
前缀最大值(原题链接)

题目描述

求一个数列的所有前缀最大值之和。
即:给出长度为n的数列a[i],求出对于所有1<=i<=n,max(a[1],a[2],…,a[i])的和。
比如,有数列:666 304 692 188 596,前缀最大值为:666 666 692 692 692,和为3408。
对于每个位置的前缀最大值解释如下:对于第1个数666,只有一个数,一定最大;对于第2个数,求出前两个数的最大数,还是666;对于第3个数,求出前3个数的最大数是692……其余位置依次类推,最后求前缀最大值得和。
由于读入较大,数列由随机种子生成。
其中a[1]=x,a[i]=(379*a[i-1]+131)%997。

输入

一行两个正整数n,x,分别表示数列的长度和随机种子。(n<=100000,x<997)

输出

一行一个正整数表示该数列的前缀最大值之和。

样例输入

5 666

样例输出

3408

提示

数列为{666,304,692,188,596},前缀最大值为{666,666,692,692,692},和为3408。

解题思路1:直接暴力求解即可:时间复杂度O(N)
AC代码1:

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 1e5 + 10;
int a[N];
int sum,maxn;//maxn用来存储下标i之前最大的数

int main()
{
    int n,x;scanf("%d%d",&n,&x);
    a[1] = x;
    for (int i = 2;i <= n;i++) a[i] = (379 * a[i - 1] + 131) % 997;//得到数列
    //for (int i = 1;i <= n;i++) cout << a[i] << " ";
    
    sum += a[1];//先加上第一个最大前缀
    maxn = a[1];
    for (int i = 2;i <= n;i++)
    {
        if (a[i] > maxn)//更新最大前缀
        {
            maxn = a[i];
        }
        sum += maxn;//加上更新后的最大前缀
    }
    cout << sum << endl;
    return 0;
    
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值