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

来源:互联网 发布:幽默app软件下载 编辑:程序博客网 时间:2024/06/05 19:33

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 5010const 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)的算法,然而并不会….

0 0