51nod 求子段和问题总结(DP)

1049 最大子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20

最基本的问题

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;


int main(){

    long long n,a,ans = -1e9,temp_sum = 0;
    cin>>n;

    for(int i = 0; i < n; i++){
        cin>>a;
        if(temp_sum < 0) temp_sum = a;
        else temp_sum += a;

        ans = max(ans,temp_sum);
    }

    cout<<ans<<endl;
    return 0;
}

1050 循环数组最大子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题 收藏 关注
N个整数组成的循环序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续的子段和的最大值(循环序列是指n个数围成一个圈,因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列)。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N+1行:N个整数 (-10^9 <= S[i] <= 10^9)
Output
输出循环数组的最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20

这个是上面的变形,这个我们还要求出最小子段和,然后用总和减去最小子段和比较最大子段和,看谁大就行

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;


int main(){

    long long n,a,ans1 = 0,ans2 = 0,maxsum =0,minsum = 0, sum = 0;
    cin>>n;

    for(int i = 0; i < n; i++){
        cin>>a;
        sum += a;
        if(maxsum < 0) maxsum = a;
        else maxsum += a;

        if(minsum > 0) minsum = a;
        else minsum += a;

        ans1 = max(maxsum,ans1);
        ans2 = min(minsum,ans2);
    }

    ans1 = ans1 > sum - ans2 ? ans1 : sum - ans2;
    cout<<ans1<<endl;
    return 0;
}

1065 最小正子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],从中选出一个子序列(a[i],a[i+1],…a[j]),使这个子序列的和>0,并且这个和是所有和>0的子序列中最小的。
例如:4,-1,5,-2,-1,2,6,-2。-1,5,-2,-1,序列和为1,是最小的。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N+1行:N个整数
Output
输出最小正子段和。
Input示例
8
4
-1
5
-2
-1
2
6
-2
Output示例
1

这题没想像中那么简单,开始以为和求最大的差不多,后来随手捏了几个数据都不行,之前是方法是累加,现在要区间求

先求一下从第一位开始的到第i位的累加,
4,-1,5,-2,-1,2,6,-2 => 4 3 8 6 5 7 13 11

对这个累加的数列排个序,然后只要判断邻近的两个数是否可以组成序列,比如4和3就不可以,因为4 > 3而4对应下标为0,3对应为1。4和5就可以

解释一下为什么只需检查相邻2个数就可以,设ABC是排序后的结果,如果A同B不能组成序列,而A同C可以组成序列,那么B同C也可以组成序列,并且BC会是一个更优的解。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;


vector<pair<long long,int> > vec;

int main(){

    int n,a,ans = 1e9;
    long long sum = 0;
    cin>>n;
    vec.push_back(make_pair(0,0));
    for(int i = 1; i <= n; i++){
        cin>>a;
        sum += a;
        vec.push_back(make_pair(sum,i));
    }

    sort(vec.begin(), vec.end());

    for(int i = 1; i <= n; i++){
        if(vec[i].second > vec[i-1].second){
            int subsum = vec[i].first - vec[i-1].first; 

            if(subsum > 0){
                ans = min(ans,subsum);
            }
        }
    }

    cout<<ans<<endl;
    return 0;
}

1052 最大M子段和
基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
例如:-2 11 -4 13 -5 6 -2,分为2段,11 -4 13一段,6一段,和为26。
Input
第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 5000)
第2 - N+1行:N个整数 (-10^9 <= a[i] <= 10^9)
Output
输出这个最大和
Input示例
7 2
-2
11
-4
13
-5
6
-2
Output示例
26

dp[i][j] 表示在前 i 个数中选取 j 组 且第 i 个数在最后一组中的 Max Sum Plus Plus
那么现在对于第i个数有两种决策
1, 第 i 个数和第 i-1 个数连接成一段 dp[i][j] = dp[i-1][j] + a[i]
2, 第 i 个数自己单独做一段 那么前面就需要有 j-1 段 dp[i][j] = max{dp[k][j-1] | j - 1<= k < i} + a[i]
那么也就有了状态转移方程 dp[i][j] = max(dp[i-1][j], max{dp[k][j-1] | j - 1<= k < i}) + a[i];

但是这样空间复杂度超了,可以用滚动数组优化
发现更新 dp[i][j] 的时候只用到了 dp[.][j] 和 dp[.][j-1] 里面的值 也就是 dp[.][0~j-2] 都已经没用了 那也就不用存这些没用的了 也就是j只用开两维就够了 也就是所谓的滚动数组了 用 dp[.][1] 和 dp[.][0] 来轮换表示 dp[.][j] 和 dp[.][j-1] 这样空间复杂度就降到了O(n) 这是可以接受的

那么我现在用两个数组来优化

用两个数组,pre[MAXN]和dp[MAXN]。
首先m次循环,第x次循环代表的是把整个序列分成x个子段所能得到的最大x子段和。
pre[i]数组记录的是,从第1个数到第i个数被分成x个子段所能得到的最大子段和。
假设当前已分成了x个子段(即最外层循环执行了x次),然后需要执行第x+1次时:
则,dp[i]的转移是从下列两种情况取max值。
1.前i-1个数分为x个子段得到的最大和pre[i-1]加上单独把input[i]作为第x+1个子段的开头;
2.从前i-1个数中已经分出了x+1个子段,且第x+1个子段的尾部需要加上input[i],即dp[i-1]+input[i]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <limits.h>
using namespace std;
typedef long long LL;
#define MAXN 5010
const LL MIN_INF = -(1 << 30);
LL dp[MAXN], input[MAXN], pre[MAXN];
int n, m;
int main() {
    scanf("%d%d", &n, &m);
    dp[0] = MIN_INF;
    pre[0] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &input[i]);
        pre[i] = 0;
        dp[i] = MIN_INF;
    }
    LL ans = MIN_INF;
    while(m--) {
        ans = MIN_INF;
        for(int i = 1; i <= n; i++) {
            if(i == 1) dp[i] = pre[i - 1] + input[i];
            else {
                if(dp[i - 1] > pre[i - 1]) {
                    dp[i] = dp[i - 1] + input[i];//第i个数与它的上一个数在同一子段内
                } else {
                    dp[i] = pre[i - 1] + input[i];//从第i个分出一个新的子段
                }
            }
            //dp[i] = max(dp[i - 1], pre[i - 1]) + input[i]; 或者直接这么取值也可
            pre[i - 1] = ans;
            ans = max(ans, dp[i]);
        }
        pre[n] = ans;
    }
    printf("%lld\n", ans);
    return 0;
}

然后这题还要听说O(n)的算法,然而并不会….

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值