luogu P2034 选择数字
对于这道题,简单粗暴的\(dp\)是不难想的,\(dp\)方程式为\(f_i=max(f_{j-1}+\sum_{s=j+1}^ia_s)\),其中\(max(i-k,0)\leq j\leq i-1\),然后就有了\(70\)分
代码实现:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
long long n,k,a[500039],sum[500039],q[500039],head,tail,f[500039],ans,tot,pus;
int main(){
register int i,j,s;
scanf("%lld%lld",&n,&k);
for(i=1;i<=n;i++)scanf("%lld",&a[i]);
f[1]=a[1];
for(i=2;i<=n;i++){
for(j=max(i-k,0);j<=i-1;j++){
ans=0;
for(s=j+1;s<=i;s++) ans+=a[s];
f[i]=max(f[i],ans+f[j-1]);
}
f[i]=max(f[i],f[i-1]);
tot=max(tot,f[i]);
}
printf("%lld\n",tot);
}
考虑前缀和优化,设\(sum\)为预处理数组,\(dp\)方程式变为\(f_i=max(f_j+sum_i-sum_j)\),可以有\(80\)分。
代码实现:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
long long n,k,a[500039],sum[500039],q[500039],head,tail,f[500039],ans,tot,pus;
int main(){
register int i,j,s;
scanf("%lld%lld",&n,&k);
for(i=1;i<=n;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
f[1]=a[1];
for(i=2;i<=n;i++){
for(j=max(i-k,0);j<=i-1;j++){
f[i]=max(f[i],sum[i]-sum[j]+f[j-1]);
}
f[i]=max(f[i],f[i-1]);
tot=max(tot,f[i]);
}
printf("%lld\n",tot);
}
我们注意到\(j\)这一重循环在反复寻找区间最值,那么想到可以单调队列优化,这么一看,似乎是单调队列板子题:维护一个单调队列,每次让\(f_{i-2}+a_i\)入队并维护单调性,设q为单调队列,那么状态转移方程:
\(f_i=max(f_{i-1},f_{q_{head+1}-1}+sum_i-sum_{q_{head+1}})\)
代码实现:
#include<cstdio>
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
long long n,k,a[500039],sum[500039],q[500039],head,tail,f[500039],ans,tot,pus;
int main(){
register int i,j,s;
scanf("%lld%lld",&n,&k);
for(i=1;i<=n;i++){
scanf("%lld",&a[i]);sum[i]=sum[i-1]+a[i];
}
f[1]=a[1];
q[++tail]=0;
for(i=2;i<=n;i++){
while(head!=tail&&i-q[head+1]>k) head++;
while(head!=tail&&f[q[tail]-1]+sum[i]-sum[q[tail]]<f[i-2]+a[i]) tail--;
q[++tail]=i-1;
f[i]=max(f[q[head+1]-1]+sum[i]-sum[q[head+1]],f[i-1]);
tot=max(tot,f[i]);
}
//for(i=1;i<=n;i++) printf("%lld\n",f[i]);
printf("%lld\n",tot);
}