Description
给定一个序列,选择其中若干个数,但不能有超过k个连续的数字被选择。最大化选出的数字之和。
Solution
dp+单调队列
正难则反,考虑从序列中取出一些数,这些数两两之间的距离不超过k,最小化之和
那么定义$f[i]$表示前i个数,取出一些的最小和(保证第i个取),那么显然有状态转移方程
$$f[i]=min{f[j]}+a[i]quad (i-k-1le j<i)$$
可以发现,对于每一个i,决策允许集合中的j都是连续的一段,并且当i增加时,决策允许集合会与之前的一部分重叠,而且转移是求出最值,所以用一个单调队列维护一下就好了、
时间复杂度为$O(n)$
Code
#include <bits/stdc++.h> // check if it is judged online namespace shl { typedef long long ll; inline int read() { int ret = 0, op = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-') op = -1; c = getchar(); } while (isdigit(c)) { ret = ret * 10 + c - '0'; c = getchar(); } return ret * op; } int n, m; ll a[100010], sum, f[100010], que[100010], h = 1, t = 1, tot; int main() { n = read(), m = read(); for (register int i = 1; i <= n; ++i) a[i] = read(), tot += a[i]; for (register int i = 1; i <= n; ++i) { f[i] = f[que[h]] + a[i]; while (h <= t && f[i] <= f[que[t]]) t--; que[++t] = i; while (h <= t && que[h] <= i - m - 1) h++; } ll ans = 0; for (register int i = n - m; i <= n; ++i) ans = std::max(ans, tot - f[i]); printf("%lld ", ans); return 0; } } int main() { #ifdef LOCAL freopen("textname.in", "r", stdin); freopen("textname.out", "w", stdout); #endif shl::main(); return 0; }