P4852 yyf hates choukapai
考虑\(dp\),设状态量\(dp[i][j]\)表示抽到\(i\)用了\(j\)次连抽的最大值。
预处理前缀和:\(sum[i]=\sum\limits_{j=1}^i a[j]\)
状转方程:\(dp[i][j]=\max\{\quad dp[i-k-c][j-1]+a[i-k-c+1]+sum[i]-sum[i-k]\quad |\quad k\in[0,d]\quad\}\)
意思是可以选长度为\(k\)的一段单抽,然后来一次连抽。
注意到没有\(i\)和\(i-k\)的关联项,故直接单调队列维护即可。
时间复杂度\(\Theta(n*(cn+m))\)
代码如下,仅供参考:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
int n,m,c,d,tot;
const int maxn=2e5+10;
int a[maxn],sum[maxn],dp[maxn][50];
int p[maxn][50],q[maxn],l,r;
inline void out(int i,int j){
if(j==0)return;
out(p[i][j]-1,j-1);
printf("%d ",p[i][j]);
}
int main(){
//freopen("P4852.in","r",stdin);
//freopen("P4852.out","w",stdout);
n=read(),m=read(),c=read(),d=read();
tot=c*n+m;
for(int i=1;i<=tot;i++){
a[i]=read();
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=d;i++)
dp[i][0]=sum[i];
for(int j=1;j<=n;j++){
memset(q,0,sizeof(q));
l=1,r=0;
for(int i=j*c;i<=tot;i++){
dp[i][j]=dp[i-c][j-1]+a[i-c+1];
p[i][j]=i-c+1;
while(l<=r&&i-q[l]>d)l++;
if(l<=r&&dp[i][j]<dp[q[l]-c][j-1]+a[q[l]-c+1]+sum[i]-sum[q[l]]){
dp[i][j]=dp[q[l]-c][j-1]+a[q[l]-c+1]+sum[i]-sum[q[l]];
p[i][j]=q[l]-c+1;
}
while(l<=r&&dp[i-c][j-1]-sum[i]+a[i-c+1]>=dp[q[r]-c][j-1]+a[q[r]-c+1]-sum[q[r]])r--;
q[++r]=i;
}
}
printf("%d\n",dp[tot][n]);
out(tot,n);
return 0;
}
深深地感到自己的弱小。