51nod 1053 最大M子段和 V2

Posted 8023spz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}

 

以上是关于51nod 1053 最大M子段和 V2的主要内容,如果未能解决你的问题,请参考以下文章

51Nod1053 最大M子段和V2 二分+DP

51nod1053&&1052 最大M子段和

51nod 1254 最大子段和 V2

51nod 1254 最大子段和 V2

51Nod-1254 最大子段和V2 题解

51nod1254 最大子段和 V2