连续最大和,数字类区间问题
2014-10-09 17:00
441 查看
例题:
-20 90 -30 -20 80 -70 -60 125
如果我们选择1到4这4个数,和为20,还可以选择6到8这3个数,和为-5,|-5|=5,该方案获得的和的绝对值最小。
输入格式:
第一行输入N,表示数字的个数。接下来N行描述这N个数字。
输出格式:
第一行输出一个整数,表示最小绝对值的和,第二行包含一个整数表示形成该绝对值和最长序列的长度。
1<= N<=100000
这样的题目,一般可以用前缀和来做。
以第一题为例子
我们设s[i]为前i个数字之和.那么s[i]-s[j] (i>j) 就为j+1到i的数字之和。这样我们就可以任意求出一段区间的和是多少。
在利用题目的条件,看看能不能利用前缀和的性质。
还是对于第一题。我们把得到的前缀和进行排序,那么可以知道min{ abs( s[i]-s[i-1] ) }(1<=i<=n)就是答案。注意排序前加入一个s[0]=0,因为s[i]-s[j] (i>j) 表示j+1到i的数字之和
因为前缀和排序后。以i为一段的区间的最小绝对值一定是 min{ abs( s
-s[n-1] ),abs( s
- s[n+1] ) } 这里的n是i在排序以后的位置
用快排nlongn就能排好,加上O(n)的扫描。
这道题有两种情况:1:i<=j 从i累加到j 。
2:从1累加到i ,再从j累加到n (i<=j)两者取和;
第一种情况:可以采用贪心的思想,上一次累加的结果如果小于0则 当前数字不接上次累加的结果,如果大于0则 接上上次的累加结果是无疑是最优的方法。
第2种情况 则是可以看做 数列的最大前缀加上最大后缀,那么思路则是:先累加f[i]=f[1]....f
;找到最大的前缀lmax=max(f[1],......f[i]);
最大后缀: rmax=max(f[i+1]....f
) 答案取max(rmax+lmax),这便是第二种情况的最优解。两种情况取max 。
介绍o(n)的算法
我们舍弃负的子节点,而且因为仅一条路,因此不必递归寻找。
即舍弃所有负权序列。
代码:
转化1:
在其后添加一条相同序列。即将N变2N,从中选取长度不超过N的序列的最大值。
但是我能想到的最好办法也要o(n^2)。
转化2:
视其为线状,记录序列和s,同时选取最大序列和最小负序列。
一定有一个不会因为环的断开而断开。结果从s-min与max中选。时间复杂度o(n);
证明:
当我们用贪心思想选取最小负序列时,加入其中的序列之和必为负,当序列和为正时即舍去。
被最小序列舍去的序列正好时最大序列所需要的。且序列为环状,故二者必然互为对全集的补集。
比如样例:2 -4 6 -1 -4 8 -1 3,最大序列为8到6,但是被断开了;最小序列为-1到-4没有被断开。
所以最大序列是s-min。即9-(-5)=14;
代码:
1、N个数排成一排,你可以任意选择连续的若干个数,算出它们的和。问该如何选择才能使得和的绝对值最小。
如:N=8时,8个数如下:-20 90 -30 -20 80 -70 -60 125
如果我们选择1到4这4个数,和为20,还可以选择6到8这3个数,和为-5,|-5|=5,该方案获得的和的绝对值最小。
输入格式:
第一行输入N,表示数字的个数。接下来N行描述这N个数字。
输出格式:
第一行输出一个整数,表示最小绝对值的和,第二行包含一个整数表示形成该绝对值和最长序列的长度。
1<= N<=100000
2、N个数围成一圈,要求从中选择若干个连续的数(注意每个数最多只能选一次)加起来,问能形成的最大的和。
N<=100000这样的题目,一般可以用前缀和来做。
以第一题为例子
我们设s[i]为前i个数字之和.那么s[i]-s[j] (i>j) 就为j+1到i的数字之和。这样我们就可以任意求出一段区间的和是多少。
在利用题目的条件,看看能不能利用前缀和的性质。
还是对于第一题。我们把得到的前缀和进行排序,那么可以知道min{ abs( s[i]-s[i-1] ) }(1<=i<=n)就是答案。注意排序前加入一个s[0]=0,因为s[i]-s[j] (i>j) 表示j+1到i的数字之和
因为前缀和排序后。以i为一段的区间的最小绝对值一定是 min{ abs( s
-s[n-1] ),abs( s
- s[n+1] ) } 这里的n是i在排序以后的位置
用快排nlongn就能排好,加上O(n)的扫描。
下面是第一题的程序源码:
#include <iostream> #include <fstream> using namespace std; int _gSum[100001]; int _gPos[100001]; int tadd,tp,ti; void _fSort( int begin,int end ) { int pos = begin; for( int i = begin+1;i < end;++i ) { if( _gSum[i] < _gSum[pos] ) { tadd = _gSum[pos+1],tp = _gSum[pos],ti = _gSum[i]; _gSum[i] = tadd; _gSum[pos+1] = tp; _gSum[pos] = ti; tadd = _gPos[pos+1],tp = _gPos[pos],ti = _gPos[i]; _gPos[i] = tadd; _gPos[pos+1] = tp; _gPos[pos] = ti; ++pos; } } if( begin < pos ) _fSort( begin,pos ); if( pos < end ) _fSort( pos+1,end ); } int main() { ifstream file_in("min.in"); int n; file_in >> n; _gSum[0] = 0; int temp; for( int i = 1;i <= n;++i ) { file_in >> temp; _gSum[i] = _gSum[i-1] + temp; _gPos[i] = i; } file_in.close(); _fSort( 0,n+1 ); int min = 123456789,size;//abs int tsum; for( int i = 1;i <= n;++i ) { tsum = abs( _gSum[i] - _gSum[i-1] ); if( tsum < min ) { size = abs( _gPos[i] - _gPos[i-1] ); min = tsum; } } cout << min << endl; cout << size << endl; return 0; }
第2题:
很容易想到o(n^2)的动态规划。这道题有两种情况:1:i<=j 从i累加到j 。
2:从1累加到i ,再从j累加到n (i<=j)两者取和;
第一种情况:可以采用贪心的思想,上一次累加的结果如果小于0则 当前数字不接上次累加的结果,如果大于0则 接上上次的累加结果是无疑是最优的方法。
第2种情况 则是可以看做 数列的最大前缀加上最大后缀,那么思路则是:先累加f[i]=f[1]....f
;找到最大的前缀lmax=max(f[1],......f[i]);
最大后缀: rmax=max(f[i+1]....f
) 答案取max(rmax+lmax),这便是第二种情况的最优解。两种情况取max 。
介绍o(n)的算法
我们舍弃负的子节点,而且因为仅一条路,因此不必递归寻找。
即舍弃所有负权序列。
代码:
long maxSubSum(const vector<int>& a) { long maxSum = 0, thisSum = 0; for (int j = 0; j < a.size(); j++) { thisSum += a[j]; if (thisSum > maxSum) maxSum = thisSum; else if (thisSum < 0) thisSum = 0; } return maxSum; }
解题思路:
因为是环状的难以处理,所以转化成线状的。转化1:
在其后添加一条相同序列。即将N变2N,从中选取长度不超过N的序列的最大值。
但是我能想到的最好办法也要o(n^2)。
转化2:
视其为线状,记录序列和s,同时选取最大序列和最小负序列。
一定有一个不会因为环的断开而断开。结果从s-min与max中选。时间复杂度o(n);
证明:
当我们用贪心思想选取最小负序列时,加入其中的序列之和必为负,当序列和为正时即舍去。
被最小序列舍去的序列正好时最大序列所需要的。且序列为环状,故二者必然互为对全集的补集。
比如样例:2 -4 6 -1 -4 8 -1 3,最大序列为8到6,但是被断开了;最小序列为-1到-4没有被断开。
所以最大序列是s-min。即9-(-5)=14;
代码:
#include<stdio.h> #include<stdlib.h> const int N=100010; int a ,n,s; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); s+=a[i]; } int ans=0,ansmax=0,ansmin=0,max=0,minn=0; for(int i=1;i<=n;i++) { max+=a[i];minn+=a[i]; if(max>ansmax) ansmax=max; else if(max<0) max=0; if(minn<ansmin) ansmin=minn; else if(minn>0) minn=0; } if(s-ansmin>ansmax) ans=s-ansmin; else ans=ansmax; printf("%d\n",ans); } </span>
相关文章推荐
- 微软:求最大连续递增数字串/时钟问题
- 51nod 1062 序列中最大的数 (打表,连续区间问题)
- 线段树之区间最大连续和问题
- 最大连续区间和问题 —— 转自purplest C++博客
- 连续数字区间问题
- 最大子数组(最大连续区间和)问题
- 分治法对最大连续和以及归并排序,分治与递归实质把问题区间区域分割成几个小区间或者小分区,一直下钻到一个元素小区解决问题
- spoj 1716...动态区间的最大连续子段和问题...点修改...
- acm-1003 求一个数组中连续区间和的最大值问题
- 百度面试题——最大连续数字串问题
- 编程求邮资最大的一个连续的区间
- 求数组中连续区间的和最大
- 求最大连续递增数字串
- 【一道笔试题】找出最连续数字的最大长度
- 连续子串的最大值(经典的DP问题)
- 最大连续和问题
- 最大连续邮资问题
- POJ 2479 最大数字连续和 动态规划
- 求和最大的连续子串问题
- HDU 1540(线段树,以一点扩展开的最大连续区间)