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;
}

深深地感到自己的弱小。

posted @ 2020-10-19 19:55  syzf2222  阅读(82)  评论(0编辑  收藏  举报