zoukankan      html  css  js  c++  java
  • 最大子段和

    此题解法多样

    1.分治

    首先,假定有区间[l..r],其中间位置为mid,其最大子段为[i..j。那么显然,ii和jj必定符合下列三种情况之一:

    只需要分别求出三种情况下的值,取其最大的即可。

    其中,很容易求出第二种情况,即求出区间[i..mid]与区间[mid+1..j],将其相加即可。复杂度O(n)

    如何处理第一种和第三种情况呢?也不难发现,第一种情况,其实就是求区间[1..mid]中的最大值,第三种情况就是求区间[mid+1..r]中的最大值。那么,只需递归求出即可。

    显然,该解法的复杂度为 O(nlogn) 通过此题是没问题的。

    附上代码

    #include<cstdio>
    int n , arr[200200]; //arr存储该序列 
    const int minn = -19260817; // 定义最小值 
    inline int Max( int a , int b) { return a > b ? a : b ;} //自定义 Max 函数(好像比stl的快一点) 
    int rec( int l , int r ) { //分治函数 
        if ( l == r ) {    //    l=r时,直接返回该位置的值 
            return arr[l];
        }
        int mid = ( l + r ) >> 1;  
        int sum = 0 , ret1 = minn , ret2 = minn; //ret1为[l..mid]区间内包含mid的最大子段和,ret2为[mid+1..r]区间内包含(mid+1)的最大子段和  
        for( int i = mid ; i >= l ; i-- ) {
            sum += arr[i];
            ret1 = Max( ret1 , sum );
        }  //求出[i..mid]区间最大值
        sum = 0;
        for( int i = mid+1 ; i <= r ; i++ ) {
            sum += arr[i];
            ret2 = Max( ret2 , sum );
        }  //求出[mid+1..r]区间最大值
        return Max( Max( rec( l , mid ) , rec( mid + 1 , r ) ) , ret1 + ret2 );   //返回可能一 可能二 可能三 中的最大值
    }
    int main() { // 以下主函数  
        scanf("%d", &n );
        for( int i = 1 ; i <= n ; i++ ) {
            scanf("%d" , &arr[i] );
        }
        printf("%d" , rec(1 , n) ); 
        return 0;
    }

    2.DP

    这道题其实是练习DP的入门题(本蒟蒻也才刚刚学DP)

    首先,通过题意,我们可以了解到:

    f[i]=max(f[i-1]+n[i],n[i])

    但是!

    f[n]的值并不一定是最终结果

    比如这个输入:

    5 233 233 -666 1 1

    如果直接输出f[n]的值,结果会是2,但是答案应该为466!

    为什么?

    因为若f[i]的值为负数,则f[i+1]的值就是n[i],而n[i]的值不一定比前面的最大字段和数大!

    (或者n[i]为负数,则f[i]小于f[i-1]!)

    所以,我们还要再用一个数从1到n再查找一次,才能找出最大数!!!

    代码(时间复杂度大概是O(n)?算了,反正我也不晓得):

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        int n[200001],p,ans[200001]={0};
        int sum=-9999999;//|x|<=10000   QWQ
        cin>>p;
        for(int i=1;i<=p;i++)
        {
            cin>>n[i];//输入
            ans[i]=max(ans[i-1]+n[i],n[i]);//DP
            sum=max(sum,ans[i]);//取最大值也同时进行,节约时间
        }
        cout<<sum;//直接输出
        return 0;
    }

    3.贪心

    我的想法也是贪心,就是用一个sum记录当前前缀和,一路累积过去,如果前缀和sum变成了负数,那么下一个数就不需要前面的数了(因为还不如只选它一个),这时把sum置为0,再继续累加。

    我写了一个挺短的代码,(从A+B题解那里学到了好多东西):

    #include<cstdio>
    int n,j,sum,maxx;int main(){         
        scanf("%d%d",&n,&maxx);sum=maxx;//输入n
        while(--n){scanf("%d",&j);sum=sum>0?sum:0;sum+=j;maxx=maxx>sum?maxx:sum;}//贪心,如果负了就舍去 
        return (printf("%d",maxx))&0;//输出并return 0 
    }

    4.贪心+DP

    因为前几天hack掉了几个思想很好但是没有注意细节的题解,所以来这填坑(包括之前题解的思想,用自己语言解释)

    DP什么的下面的dalao们都讲得够明白了,这里仅介绍空间上的优化

    因为是DP,所以不用储存从第一个到最后一个的答案,可以使用滚动数组,仅两个元素,轮流储存。每次通过前一个算出后一个,然后将前一个删除

    代码如下:

    #include<cstdio>
    #include<cmath>
    #include<iostream>
    using namespace std;
    int sum,a[2],n;//仅需要两个元素的数组
    int main()
    {
        scanf("%d",&n);
        scanf("%d",&a[1]);//先读入第一个元素
        sum=a[1];
        for(int i=2;i<=n;++i)
        {
            if(sum<0) sum=0;//因为第一个输入的数可能是负数,所以这个判断放for循环的最前面
            scanf("%d",&a[i%2]);//滚动
            sum+=a[i%2];
            a[i%2]=max(a[(i-1)%2],sum);//DP状态转移方程
        }
        printf("%d",a[n%2]);
        return 0;
    } 
  • 相关阅读:
    清明节实现所有网页变灰色
    点击按钮,复制文本
    Matlab笔记
    spring框架中配置mysql8.0需要注意的地方(转载)
    移动吉比特H2-2光猫超级用户与密码
    JS关闭chorme页面
    MATLAB利用solve函数解多元一次方程组
    微信聊天记录导出为csv,并生成词云图
    fmt.Sprintf(格式化输出)
    iris,context源码分析
  • 原文地址:https://www.cnblogs.com/fangzm/p/14105407.html
Copyright © 2011-2022 走看看