最大连续部分和(原题链接)
题目描述
有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] = -2
,dp[2] = 11
,dp[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、j
。i
指针指向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;
}