bzoj2288 【POJ Challenge】生日礼物

【POJ Challenge】生日礼物

Time Limit: 10 Sec Memory Limit: 128 MB

Description

ftiasch 18岁生日的时候,lqp18_31给她看了一个神奇的序列 A1, A2, ..., AN. 她被允许选择不超过 M 个连续的部分作为自己的生日礼物。
自然地,ftiasch想要知道选择元素之和的最大值。你能帮助她吗?
1330366-20180517115203803-2139196648.jpg

Input

第1行,两个整数 N (1 ≤ N ≤ 105) 和 M (0 ≤ M ≤ 105), 序列的长度和可以选择的部分。
第2行, N 个整数 A1, A2, ..., AN (0 ≤ |Ai| ≤ 104), 序列。

Output

一个整数,最大的和。

Sample Input

5 2
2 -3 2 -1 2

Sample Output

5

<br >
<br >
<br >
<br >
<br >
<br >

抱着线段树的心态来做这道题。。。结果怎么想怎么贪心。。。
先说一下步骤:
先同号的合并,然后把前后的负数直接去掉(最简单的贪心策略)
然后就是一通操作了。。。。先把所有正项加起来,然后特判一下之后,把每个数的绝对值存进堆里。
小根堆贪心,如果这个数原本是个正数,那么减去他代表不选他,如果是个负数,那么减去他代表合并他的左右两个。
为什么是对的呢?我们可以大致脑补一下:
首先,如果全是正数,显然去掉最小的。
先说为什么无脑选最小的正项删去不对:
假设已经初步处理过的数列是:6 -4 3 -2 7 选两段
如果我们直接去掉3的话就是13,但是如果我们合并后三项的话可以取到14.
那么这种bug什么时候会出现呢?我真的觉得我的破脑袋里不知道装了些啥,反正我是这样瞎想的:
假设我有一个数列A;
如果我把这个数列中的一个负项变成0,这个新数列B的答案不可能变的更差,对吧。
紧接着,我们把B中的一个正项变的更大,这样也不会让答案变的更差。
其实我们把一个负数进行合并就是这么一个过程。去掉一个负项和两个正项,然后加一个更好的正项,(在m允许的范围内是正确的)
这样,我们稍微思考一下就会发现,在m允许的情况下,上述方法是正确的。
(大致感受一下就好了。。。)

<br >
<br >


#include<bits/stdc++.h>
#define lpl pair<int, int>
using namespace std;
const int maxn = 1e5 + 5, INF = 211199921;
bool flag[maxn];
int n, m, s, t, ans, ini[maxn], num[maxn], l[maxn], r[maxn];
priority_queue< lpl, vector<lpl>, greater<lpl> > q;

inline int read()
{
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){ x = 10 * x + ch - '0'; ch = getchar();}
    return x * f;
}

inline void putit()
{
    t = read(); m = read(); s = 1;
    for(int i = 1; i <= t; ++i){
        ini[i] = read();
    } 
    while(ini[t] <= 0) t--;
    while(ini[s] <= 0) s++;
    for(; s <= t; s++)
    if((ini[s] > 0 && ini[s - 1] > 0) || (ini[s] <= 0 && ini[s - 1] <= 0))
    num[n] += ini[s]; else num[++n] = ini[s];
    //for(int i = 1; i <= n; ++i) printf("%d ", num[i]);
}

inline void workk()
{ 
    for(int i = 1; i <= n; ++i)
        if(num[i] > 0) {ans += num[i], m--;}
        else num[i] = -num[i];
    if(m >= 0){cout << ans; return;}
    for(int i = 1; i <= n; ++i){
        l[i] = i - 1, r[i] = i + 1, q.push(lpl(num[i], i));
    }
    m = -m; r[n] = 0; int x;
    while(m--){
        while(q.top().first != num[q.top().second]) q.pop();
        x = q.top().second; q.pop(); ans -= num[x];
        if(!l[x]){
            num[r[x]] = INF; l[r[x]] = 0; continue;
        }
        if(!r[x]){
            num[l[x]] = INF; r[l[x]] = 0; continue;
        }   
        num[x] = num[l[x]] + num[r[x]] - num[x];
        num[l[x]] = num[r[x]] = INF;
        l[x] = l[l[x]]; r[x] = r[r[x]]; r[l[x]] = l[r[x]] = x;
        q.push(lpl(num[x], x));
    }
    cout << ans;
}

int main()
{
    putit();
    workk();
    return 0;
}

转载于:https://www.cnblogs.com/LLppdd/p/9050370.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值