洛谷P1484 种树

解法有wqs二分和反悔贪心
反悔贪心一般的形式就是通过一个优先队列来贪心
做法很巧妙,优先队列维护最大值,当你拿了 \(a_i\),显然只可能 \(a_{i-1} + a_{i + 1} > a_i\),那么可以把 \(a_{i-1}+a_{i+1}-a_i\)看作一个整体再放进去

int a[maxn], l[maxn], r[maxn];
bool vis[maxn];
void run() {
    int n, k; scanf("%d %d", &n, &k);
    priority_queue<pair<ll, int>> q;
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
        l[i] = i - 1;
        r[i] = i + 1;
        q.push({a[i], i});
    }
    l[1] = 1; r[n] = n;
    ll ans = 0;
    while(k --) {
        while(vis[q.top().second])  q.pop();
        pair<ll, int> top = q.top();    q.pop();
        if(top.first < 0)   break;
        ans += top.first;   int id = top.second;
        a[id] = a[l[id]] + a[r[id]] - a[id];
        top.first = a[id];
        vis[l[id]] = vis[r[id]] = 1;
        l[id] = l[l[id]];   r[id] = r[r[id]];
        if(id != l[id]) r[l[id]] = id;
        if(id != r[id]) l[r[id]] = id;
        q.push(top);
    }
    printf("%lld\n", ans);
    return ;
}

题目中有约束条件是小于k棵树的最大值,也符合凸函数的特性
可以通过wqs二分求解凸函数最优解

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 5e5 + 10;

ll dp[maxn][2];
int a[maxn], n, k;
void run() {
    int n, k; scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
        for(int j = k; j >= 0; -- j) {
            dp[j][0] = max(dp[j][0], dp[j][1]);
            if(j)   dp[j][1] = dp[j - 1][0] + a[i];
        }
    }
    printf("%lld\n", max(dp[k][0], dp[k][1]));
}

int g[maxn][2];
pair<ll, int> run1(int c) {
    for(int i = 1; i <= n; ++ i) {
        dp[i][1] = dp[i - 1][0] + a[i] + c;
        g[i][1] = g[i - 1][0] + 1;
        if(dp[i - 1][1] > dp[i - 1][0])         dp[i][0] = dp[i - 1][1], g[i][0] = g[i - 1][1];
        else if(dp[i - 1][1] < dp[i - 1][0])    dp[i][0] = dp[i - 1][0], g[i][0] = g[i - 1][0];
        else                                    dp[i][0] = dp[i - 1][0], g[i][0] = min(g[i - 1][0], g[i - 1][1]);//拿最少//max(g[i - 1][0], g[i - 1][1]);//拿最多
    }
    ll ans; int cnt;
    if(dp[n][0] > dp[n][1])         ans = dp[n][0], cnt = g[n][0];
    else if(dp[n][0] < dp[n][1])    ans = dp[n][1], cnt = g[n][1];
    else                            ans = dp[n][1], cnt = min(g[n][0], g[n][1]);//拿最少//cnt = max(g[n][0], g[n][1]);//拿最多
    return pair<ll, int>{ans, cnt};
    //不能拿最多的,会错,当然如果判断[l(c), r(c)]当然是可以对的
}

void solve() {
    scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; ++ i)    scanf("%d", &a[i]);
    pair<ll, int> res = run1(0);
    if(res.second <= k) {//大于0的比k个少
        printf("%lld\n", res.first);
        return ;
    }
    int l = -1000000, r = -1;
    while(l <= r) {
        int mid = l + r >> 1;
        pair<ll, int> res = run1(mid);//二分的含义是所有值减去mid后的 最大值中拿的个数最少
        if(res.second <= k) l = mid + 1;//求得的cnt是l(c)
        else                r = mid - 1;
    }
    res = run1(l - 1);
    printf("%lld\n", res.first - 1ll * (l - 1) * k);
}

signed main() {
    int t = 1; //scanf("%d", &t);
    while(t--)  solve();
    return 0;
}
posted @ 2021-07-30 20:47  wlhp  阅读(15)  评论(0)    收藏  举报