P1484 种树 经典反悔贪心
题意
直线上有(n)个坑,这(n)个坑都可以种树,至多可以种(k)棵树,且不能在相邻的坑种树,每个坑位都有一个获利值,求怎样种可以让获利最大。
[1 leq n leq 5e5\
k leq n / 2\
-1e6 leq w_i leq 1e6
]
分析
容易想到的DP做法:(dp[i][j])表示前(i)棵树,种了(j)棵的最大获利。
[dp[i][j] = max(dp[i - 1][j],dp[i - 2][j - 1] + w[i])
]
这样的DP是(n^2)的,当然,据说可以用WQS二分来优化。
但是此题也是经典的反悔贪心:
容易想到,每三个位置,要么选中间的,要么选两边的,可是贪心的选择较大的不一定是最优解。
维护策略:维护一个大顶堆,每次取出一个点时,同时加入一个点,这个点的权值是左边权值+右边权值-当前权值,同时删除两边的点,(用双向链表维护即可)这样就让后悔机制自洽了。
复杂度O(nlogn)
代码
#include<bits/stdc++.h>
#define pii pair<ll,int>
#define eps 1e-7
#define equals(a,b) (fabs(a - b) < eps)
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
const ll MOD = 1e9 + 7;
ll rd(){
ll x = 0;
int f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
struct P{
ll val;
int l,r;
};
P p[maxn];
bool vis[maxn];
priority_queue<pii> q;
void del(int x){
p[x].l = p[p[x].l].l;
p[x].r = p[p[x].r].r;
p[p[x].l].r = x;
p[p[x].r].l = x;
}
int main(){
int n = rd();
int m = rd();
for(int i = 1;i <= n;i++){
p[i].val = rd();
p[i].l = i - 1;
p[i].r = i + 1;
q.push(make_pair(p[i].val,i));
}
ll res = 0;
while(m--){
while(vis[q.top().se])
q.pop();
pii u = q.top();
q.pop();
if(u.fi <= 0) break;
res += u.fi;
vis[p[u.se].l] = vis[p[u.se].r] = 1;
p[u.se].val = p[p[u.se].l].val + p[p[u.se].r].val - p[u.se].val;
q.push(make_pair(p[u.se].val,u.se));
del(u.se);
}
cout << res;
}