差值dp 小明和广告牌
动态规划入门题
怎么看着有点像小木棍?
考虑朴素做法,我们设状态为 \(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;
}