题目描述
有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#include #include 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最长不下降子序列LIS(原题链接)#include #include 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; }
题目描述
设有由n个不相同的整数组成的数列,记为: a(1)、a(2)、……、a(n)且a(i)<>a(j) (i<>j)。例如3,18,7,14,10,12,23,41,16,24。若存在i1
输入
第一行为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前缀最大值(原题链接)#include #include 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#include 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; }



