Luogu 1792 算是双倍经验。
我们考虑对于一个点,我们要么选它,要么选它周围的两个点。
所以我们考虑用一个堆来维护,每次从堆顶取出最大值之后我们把它的权值记为:它左边的权值加上它右边的权值减去它自己的权值。即$a_{pos} = a_{l(pos)} + a_{r(pos)} - a_{pos}$。然后把它丢到堆里去。
这样子如果下次取出来这个值就相当于不选原来选过的那个点,而改选它旁边的两个点,而这样选的总的点数也是一样的,这个过程也可以扩展到一个区间,所以这样子可以求出最优解。
对于那些取出来的点的两边的点,我们下次在取出来的时候不应该再计算贡献,所以记一个$vis$。
这个“左边的点”和“右边的点”可以用链表维护。
注意到本题中不一定选满$k$个,所以当取出来的堆顶权值为负的时候直接$break$掉,而在Luogu1792中一定要选满$k$个。
时间复杂度$O(klogn)$。
Code:
#include <cstdio> #include <cstring> #include <queue> using namespace std; typedef long long ll; const int N = 5e5 + 5; int n, K, nxt[N], pre[N]; ll a[N]; bool vis[N]; template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > '9'|| ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } struct Node { ll val; int pos; Node(ll v = 0, int p = 0) {val = v, pos = p;} friend bool operator < (const Node &x, const Node &y) { return x.val < y.val; } }; priority_queue <Node> Q; inline void del(int p) { nxt[pre[p]] = nxt[p]; pre[nxt[p]] = pre[p]; vis[p] = 1; } int main() { read(n), read(K); for(int i = 1; i <= n; i++) { read(a[i]); nxt[i] = i + 1, pre[i] = i - 1; Q.push(Node(a[i], i)); } ll ans = 0LL; for(; K--; ) { for(; vis[Q.top().pos]; Q.pop()); Node out = Q.top(); Q.pop(); if(out.val < 0) break; int x = pre[out.pos], y = nxt[out.pos]; ans += out.val; Q.push(Node(a[out.pos] = a[x] + a[y] - out.val, out.pos)); del(x), del(y); } printf("%lld ", ans); return 0; }