DP的优化

andyl / 2023-07-17 / 原文

P3287 [SCOI2014] 方伯伯的玉米田

首先容易分析出一个性质:拔高玉米时,拔高 \([i,n]\) 区间的玉米一定是最优的。然后就有了一个暴力DP:

\(f[i][j]\) 表示对于前 \(i\) 个玉米(第 \(i\) 个玉米保留),拔高 \(j\) 次最多能保留多少玉米。

状态转移方程:

\(if(a[l]>a[i]\&\&a[l]-a[i]<=j):f[i][j]=max(f[l][j-(a[l]-a[i])])+1\)

\(if(a[l]<a[i]):f[i][j]=max(f[l][j])+1\)

然后你就可以拿到10分的好成绩

考虑优化。我们发现,状态转移需要求二维前缀的最大值,所以考虑二维树状数组优化。然而, \(a[l]-a[i]<=j\) 导致不能直接转移,所以有必要重新考虑状态,使得转移没有条件限制。

\(dp[i][j][k]\) 表示对于前 \(i\) 个玉米(第 \(i\) 个玉米保留),拔高 \(j\) 次,第 \(i\) 个玉米高度为 \(k\) 时,最多能保留多少玉米。

状态转移方程:\(dp[i][j][a[i]+j]=max(dp[i-1][p][q]),1<=p<=j,1<=q<=a[i]+j\)

然后就可以愉快地使用二维树状数组了。

\(code:\)

#include<iostream>
#include<cstdio>
using namespace std;
int n,k,maxn,now[50005],a[500005],dp[505][5505],ans;
int ask(int x,int y){
	int re=0;
	for(int i=x;i;i-=i&(-i))
		for(int j=y;j;j-=j&(-j))
			re=max(re,dp[i][j]);
	return re;
}
void add(int x,int y,int z){
	for(int i=x;i<=k+1;i+=i&(-i))
		for(int j=y;j<=k+maxn;j+=j&(-j))
			dp[i][j]=max(dp[i][j],z);
}
int main(){
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]),maxn=max(maxn,a[i]);
	for(int i=1;i<=n;++i){
		for(int j=0;j<=k;++j){
			int t=ask(j+1,a[i]+j)+1;//这里写j+1是避免j==0时树状数组死循环 
			now[j]=t;
			ans=max(ans,t);
		}
		for(int j=0;j<=k;++j)
			add(j+1,a[i]+j,now[j]);
	}
	/*for(int i=1;i<=n;++i)//注释掉的部分是暴力DP 
		for(int j=0;j<=k;++j){
			for(int l=0;l<i;++l){
				if(a[l]>a[i]&&a[l]-a[i]<=j)
					dp[i][j]=max(dp[i][j],dp[l][j-(a[l]-a[i])]+1);
				else if(a[l]<=a[i])
					dp[i][j]=max(dp[i][j],dp[l][j]+1); 
			}*/
	printf("%d\n",ans);
	return 0;
}