zoukankan      html  css  js  c++  java
  • 连续最大和,数字类区间问题

    例题:

    1、N个数排成一排,你可以任意选择连续的若干个数,算出它们的和。问该如何选择才能使得和的绝对值最小。

    如:N=8时,8个数如下:

    -20   90  -30   -20   80  -70  -60   125

    如果我们选择144个数,和为20,还可以选择683个数,和为-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[n]-s[n-1] ),abs( s[n] - 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[n];找到最大的前缀lmax=max(f[1],......f[i]); 最大后缀: rmax=max(f[i+1]....f[n]) 答案取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],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
    ",ans);
    }  </span>


    
    


  • 相关阅读:
    spoj 694 求一个字符串中不同子串的个数
    Qt for Android 开发大坑
    HDUOJ A Mathematical Curiosity 1017
    Node.js开发入门—HelloWorld再分析
    GTK入门学习:布局容器之固定布局
    彻底领悟javascript中的exec与match方法
    JQuery中attr属性和jQuery.data()学习笔记
    正则表达式-验证带千分号的,带任意位小数的数字类型
    JQuery EasyUI 动态改变表单项的验证守则
    JavaScript计算两个日期的时间差
  • 原文地址:https://www.cnblogs.com/tham/p/6827320.html
Copyright © 2011-2022 走看看