51Nod1115 最大M子段和V3 (可撤销贪心 + 堆 + 双向链表)

Description

给的一个长度为 n n 环形序列 a a ,求从中选 m m 个可以为空的子段的最大和。

2 n , m 1 0 5 , 1 0 9 a i 1 0 9 2 \leq n, m \leq 10^5,-10^9 \leq a_i \leq 10^9

Solution

先想到的可能是断环为链后 d p dp f i , j f_{i,j} 表示划分了 i i 段,最后一段以 j j 结尾的最大和。每个数要么新开一段,要么加入第 j j 段。转移时维护前缀最大值。然后滚动数组优化空间。可是时间爆炸。

dp 做不了考虑贪心

删掉 0 0 ,那么 a a 只有正数和负数。将连续的正数加起来,连续的负数加起来,得到新的正负交替的环形序列 a a 。最好是选所有的正数,但是正数的个数 可能 > m >m 。那么减少正数个数有两个方法

  1. 删一个整数,减少的值为这个正数,减少了一段。
  2. 将一个负数与它两边的正数合并,减少的值为负数的相反数,两端变一段。

假设正数的个数为 k k > m k \land k > m 。将所有的正数变为负数,问题转换成正数之和 +   a + \ a k m k-m 个不相邻的数的最大和。考虑如何在 O ( n log n ) O(n \log n) 内求最大和。我会时间爆炸的 dp!

dp 做不了考虑贪心

可撤销贪心。用大根堆维护数字和下标,用双向链表维护 a a 。每次取堆首 a x a_x ,但可能取 a x a_x 的前驱 a p r e a_{pre} 和后继 a n x t a_{nxt} 更优。

所以将 a p r e a x a n x t a_{pre} \leftrightarrow a_x \leftrightarrow a_{nxt} 换成 a x = a p r e a x + a n x t a_x = a_{pre} - a_x + a_{nxt} ,并且标记 a p r e a_{pre} a n x t a_{nxt} 不能选了。将新的 a x a_x 放回堆中。如果取 p r e pre n x t nxt 更优,那么下一次会从堆首取出新的 a x a_x ,而 a x + a p r e a x + a n x t = a p r e + a n x t a_x + a_{pre} - a_x + a_{nxt} = a_{pre} + a_{nxt}

进行 m m 次这样的操作,最后取出的 a x a_x 之和为答案。

不要忘记 long long。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
inline int read() {
    int x = 0, f = 0;
    char ch = 0;
    while (!isdigit(ch)) f |= ch == '-', ch = getchar();
    while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
    return f ? -x : x;
}
int _n, m, n, cnt;
ll a[N], ans = 0;
int vis[N], pre[N], nxt[N];
priority_queue<pair<ll, int> > q;
int main() {
    scanf("%d%d", &_n, &m);
    for (int i = 1; i <= _n; i++) {
        int x = read();
        if (!n || (x >= 0) != (a[n] >= 0)) a[++n] = x;
        else a[n] += x;
    }
    if ((a[1] >= 0) == (a[n] >= 0)) a[1] += a[n--];
    for (int i = 1; i <= n; i++) {
        pre[i] = i - 1, nxt[i] = i + 1;
        if (a[i] >= 0) ans += a[i], a[i] = -a[i], cnt++;
        q.push(make_pair(a[i], i));
	}
	nxt[n] = 1, pre[1] = n;
	m = cnt - m;
	if (m <= 0) {
		printf("%lld\n", ans); return 0;
	}
    while (m--) {
        int x = q.top().second; q.pop();
        if (vis[x]) {
        	m++; continue;
        }
        ans += a[x];
        a[x] = a[pre[x]] + a[nxt[x]] - a[x];
        q.push(make_pair(a[x], x));
        nxt[pre[pre[x]]] = pre[nxt[nxt[x]]] = x;
        vis[pre[x]] = vis[nxt[x]] = 1;
        pre[x] = pre[pre[x]], nxt[x] = nxt[nxt[x]];
    }
    printf("%lld\n", max(0ll, ans));
    return 0;
}
发布了41 篇原创文章 · 获赞 39 · 访问量 1744

猜你喜欢

转载自blog.csdn.net/qq_39984146/article/details/104422526