51nod 1053 最大M子段和 V2

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。

收起

输入

第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 50000)
第2 - N+1行:N个整数(-10^9 <= a[i] <= 10^9)

输出

输出这个最大和

输入样例

7 2
-2
11
-4
13
-5
6
-2

输出样例

26

v2果然是难了,还用dp就超时了,学无止境,本道题采用特别巧妙的贪心思想的方法,把相邻的正数或者负数,都加到一起,形成新的序列,也就是正数和负数交叉的序列,假如原序列是1 2 -3 -4 3,压缩后:3 -7 3,存到一个新的数组里,我们用ans记录所有正数的和,显然新数组里每一项都是一段,我们还要记录所有正数段得到个数,我们要保持正数段的个数在m之内,所以我们把所有的数段的绝对值,以及位置,存到一个set里,也可以用链表,这样做的目的是用负数填补空缺,从而把两个正数段合并为一段,我们需要知道每一段左右分别是谁,方便合并,不是单纯的下表加1和减1,因为合并多了,中间会有很多无效的位置,当然了用链表就不需要考虑这些问题,直接删除节点即可。那么怎么来合并呢,set里存绝对值和位置组成的pair,这样排序就按照绝对值排序了,每次选择绝对值最小的,假如这个数是正的,那么就用ans减去,然后跟两边的合并,因为set里所有的负数绝对值都比他大,所以ans肯定不包括它了,如果是负数,也是ans减去绝对值,负数跟两边的整数合并了,实际上让ans减少了。代码:
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
int n,m;
ll d,last;
ll s[50005];
int l[50005],r[50005];
int sc;
void modify(int cur) {///修改左右相邻结点的下标
    int ll = l[cur],rr = r[cur];
    if(ll) {
        r[ll] = rr;
    }
    if(rr) {
        l[rr] = ll;
    }
}
int main() {
    while(~scanf("%d%d",&n,&m)) {
        ll sum = 0,ans = 0,c = 0;
        set<pair<ll,int> > ss;
        for(int i = 0;i < n;i ++) {
            scanf("%lld",&d);
            if(d * last < 0) {
                s[++ sc] = sum;
                if(sc == 1 && sum < 0) sc --;
                sum = d;
            }
            else sum += d;
            last = d;
        }
        if(sum > 0) s[++ sc] = sum;
        c = (sc + 1) / 2;
        for(int i = 1;i <= sc;i ++) {
            ss.insert(make_pair(abs(s[i]),i));
            ans += (s[i] > 0 ? s[i] : 0);
            l[i] = i - 1;
            r[i] = i + 1;
        }
        r[sc] = 0;
        while(c > m) {
            int cur = ss.begin() -> second;
            ss.erase(ss.begin());
            if(s[cur] < 0 && (!l[cur] || !r[cur])) continue;///如果是负数,而且是处在首尾的位置那么就没必要合并了。
            ans -= abs(s[cur]);
            s[cur] += s[l[cur]] + s[r[cur]];
            if(l[cur]) {
                ss.erase(make_pair(abs(s[l[cur]]),l[cur]));
                modify(l[cur]);
            }
            if(r[cur]) {
                ss.erase(make_pair(abs(s[r[cur]]),r[cur]));
                modify(r[cur]);
            }
            if(s[cur]) ss.insert(make_pair(abs(s[cur]),cur));
            c --;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/8023spz/p/10912135.html

时间: 2024-10-08 20:29:53

51nod 1053 最大M子段和 V2的相关文章

51nod 1065 最小正子段和

题目链接:51nod 1065 最小正子段和 房教说用前缀和做,然后看了别人博客懂了后就感觉,这个真有意思... 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N = 50001; 6 const int inf = 0x3f3f3f3f; 7 pair<long long, int> sum[N]; 8 int

51nod 1254 最大子段和 V2

N个整数组成的序列a[1],a[2],a[3],…,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的.当所给的整数均为负数时和为0. 例如:{-2,11,-4,13,-5,-2, 4}将 -4 和 4 交换,{-2,11,4,13,-5,-2, -4},最大子段和为11 + 4 + 13 = 28. 收起 输入 第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N + 1行:N个整数(-10^9 <

51nod 1052最大M子段和 &amp; poj 2479最大两子段和

最大子段和经典问题的扩展. 题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1052 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,中间用空格分

51nod 1084 矩阵取数问题 V2

1084 矩阵取数问题 V2 基准时间限制:2 秒 空间限制:131072 KB 一个M*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,先从左上走到右下,再从右下走到左上.第1遍时只能向下和向右走,第2遍时只能向上和向左走.两次如果经过同一个格子,则该格子的奖励只计算一次,求能够获得的最大价值. 例如:3 * 3的方格. 1 3 3 2 1 3 2 2 1 能够获得的最大价值为:17.1 -> 3 -> 3 -> 3 -> 1 -> 2 -> 2 -&g

51nod 1132 覆盖数字的数量 V2

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1132 题意是给定a,b,l,r求[l,r]内有几个整数可以表示成ax+by(x,y为非负整数). 直接算l<=ax+by<=r会重复计算一个数的多种表示方法,而两种表示方法(x,y)总是相差k*(b,-a),因此限制y取最小值进行去重 即x>=0,y>=0,l<=ax+by<=r,y<a/gcd(a,b)五个半平面的交的整点个数,可以分类

最大M子段和 V2

51nod1053 这题还是我们熟悉的M子段和,只不过N,M<=50000. 这题似乎是一个堆+链表的题目啊 开始考虑把所有正数负数锁在一起. 比如: 1 2 3 -1 –2 -3 666 缩成 6 -6 666这样. 然后用一个堆来维护,就是说把所有的负数和正数都扔进堆里,先选所有正数,然后每一次把堆中绝对值最小的数(如果是负数且没有左或右就跳过)和两边合并,链表维护一下. 当然实际实现用的是set- #include <iostream> #include <stdio.h&g

51nod 1218 最长递增子序列 V2(dp + 思维)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1218 题解:先要确定这些点是不是属于最长递增序列然后再确定这些数在最长递增序列中出现的次数,如果大于1次显然是可能出现只出现1次肯定是必然出现.那么就是怎么判断是不是属于最长递增序列,这个只要顺着求一下最长递增标一下该点属于长度几然后再逆着求一下最长递减标一下该点属于长度几如果两个下标之和等于最长长度+1那么该点就属于最长递增序列,然后就是求1-len(le

51nod 1052 最大M子段和

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. 收起 输入 第1行:2个数N和M,中间用空格分隔.N为整数的个数,M为划分为多少段.(2 <= N , M <= 5000) 第2 - N+1行:N个整数 (-10^9 <= a[i] <= 10^

51nod 1052 最大M子段和(动态规划)

分析:记dp[x][s][1]为从第x个数开始,剩余s段可以分,1表示x跟上一段连着,0表示不连着,递推式为dp[x][s][1]=max{dp[x+1][s][1]+a[x],dp[x+1][s][0]},dp[x][s][0]=max{dp[x+1][s-1][1]+a[x],dp[x+1][s][0]}. 1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 const int maxn=5005;