Luogu 1484 种树

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;
}
View Code
原文地址:https://www.cnblogs.com/CzxingcHen/p/9617800.html