种树
Description
给定 \(n\) 个物品,带权。选择其中不相邻的至多 \(k\) 个,最大化权值和。
Solution
首先有一个经典的链表加反悔贪心的做法。容易发现,若 \(a_i\geq\max ( a_{i-1} , a_{i+1})\),那么要么同时选 \(i-1\) 和 \(i+1\),要么同时不选(考虑只选其中一个的情况,那么选中间那个显然答案不会更劣)。所以当我们选了 \(a_i\) 之后,便将 \(a_i\) 从序列中删除,同时将 \(a_{i-1}\) 和 \(a_{i+1}\) 在序列中合并,新的值为 \(a_{i-1}+a_{i+1}-a_i\)。可以用双向链表来维护这个过程。
主要学习一下 wqs 二分的做法。令 \(g_{k}\) 表示选择恰好 \(k\) 个物品且满足所有限制时的最大收益。若所有点 \((i,g_i)\) 恰好构成一个凸壳,且如果不限制选的物品数时,我们能快速求出最大的收益,和取到该收益时所选取的物品数,那么此时 wqs 二分便可以求出对于特定的 \(k_0\),\(g_{k_0}\) 的值。具体而言,我们二分一个斜率 \(c\),过每个点,作一条斜率为 \(c\) 的直线,那么纵截距便是 \(g_k-ck\)。观察最大的那个纵截距,会发现,它相当就是把每个物品权值都减去 \(c\) 且不限制物品数量时的最大收益。把该值加上 \(ck\) 就能得到 \(g_k\)。由于凸性,求出的 \(k\) 值关于斜率 \(c\) 是单调的,调整斜率的走向即可。
注意可能有多个点在同一条线上的情况,因为并不是严格的凸壳。此时我们 check 的时候尽量多选数即可。
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef double db;
const int N=5e5+7;
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag*x;
}
ll dp[N][2];
int f[N][2],a[N],b[N],n,k_0;
pair<ll,int> solve(int c){
for(int i=1;i<=n;i++)
b[i]=a[i]-c;
for(int i=1,op=0;i<=n;i++){
if(dp[i-1][0]!=dp[i-1][1])
op=dp[i-1][0]<dp[i-1][1];
else op=f[i-1][0]<f[i-1][1];
dp[i][0]=dp[i-1][op];
f[i][0]=f[i-1][op];
dp[i][1]=dp[i-1][0]+b[i];
f[i][1]=f[i-1][0]+1;
}
bool op;
if(dp[n][0]!=dp[n][1])
op=dp[n][0]<dp[n][1];
else op=f[n][0]<f[n][1];
return make_pair(dp[n][op],f[n][op]);
}
int main(){
n=read(),k_0=read();
int l=1,r=0,p=-1;
for(int i=1;i<=n;i++)
r=max(r,a[i]=read());
ll ans=0;
while(l<=r){
int mid=(l+r)>>1;
pair<ll,int> t=solve(mid);
if(t.second>=k_0){
ans=t.first+1ll*mid*k_0;
p=t.second,l=mid+1;
}else r=mid-1;
}
if(~p) printf("%lld",ans);
else printf("%lld",solve(0).first);
}

浙公网安备 33010602011771号