差值dp 小明和广告牌

动态规划入门题

image

怎么看着有点像小木棍?

考虑朴素做法,我们设状态为 \(dp[i][j][k]\)\(i\) 个钢筋,组成的第一根钢筋的长度为 \(j\),组成的第二根钢筋的长度为 \(k\),我们用状态储存这对组合有没有过,答案就是所有 \(j==k\) 中存在的最大的答案。

有动态转移方程:

放第一个上:\(dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a[i]][k])\);

放第二个上:\(dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-a[i]])\);

两根都不放:\(dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k])\);

总复杂度为 \(O(sum^2\times n)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
const int mod=1e9+7;
int n,sum; 
int dp[100][100][100];
int a[N]; 

int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	dp[0][0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=sum;j++){
			for(int k=0;k<=sum;k++){
				dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k]);
				if(j>=a[i]){
					dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a[i]][k]);
				}
				if(k>=a[i]){
					dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-a[i]]);
				}
			}
		}
	}
	int ans=0;
	for(int j=0;j<=sum;j++){
		for(int k=0;k<=sum;k++){
			if(dp[n][j][k]==1&&(j==k)){
				ans=max(ans,j);
			}
		}
	}
	cout<<ans;
    return 0;
}

但上面那个方法复杂度太大,不可以!

考虑用差值 dp,我们设状态为 \(dp[i][j]\) 为选前 i 个钢筋,第一根钢筋的长度减去第二根钢筋的长度的差,然后状态里面存放的是两根钢筋的总长度。

因为是差值,但数组肯定不能下标为负数吧,所有 \(j\) 初始转移为 \(sum\)(钢筋长度总和),在用 \(v\) 来调整边界。转移和上面的类似。

这样的复杂度就变为了 \(O(sum\times n)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
int n,sum; 
int dp[102][100005];
int a[102]; 

int main(){
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	int v=0;
	memset(dp,-1,sizeof dp);//表示不可达 
	dp[0][sum]=0;//和为0 
	for(int i=1;i<=n;i++){
		for(int j=sum-v;j<=sum+v;j++){
			if(dp[i-1][j]>=0){//上一个要可达才转移 
				dp[i][j]=max(dp[i][j],dp[i-1][j]);
				dp[i][j+a[i]]=max(dp[i][j+a[i]],dp[i-1][j]+a[i]);
				dp[i][j-a[i]]=max(dp[i][j-a[i]],dp[i-1][j]+a[i]);	
			}	
		}
		v+=a[i];//控制和限制状态转移的范围,防止越界 
	}
	cout<<dp[n][sum]/2;
    return 0;
}
posted @ 2024-09-06 11:20  sad_lin  阅读(6)  评论(0编辑  收藏  举报