【题解】 P1651 塔
解法一:
f[i][j][k] = 0/1表示使用前i个水晶,第一个塔高j,第二个塔高k,是否可行。
决策:第i个水晶放不放,放在哪。
f[i][j][k] = f[i - 1][j][k] || f[i - 1][j - a[i]][k] || f[i - 1][j][k - a[i]]
解法二:
f[i][j] 表示从前i个积木中选,两组高度差为j, 高度较小的那一个塔高度最大是多少。
当两组双塔的高度差一样时,我们只将更高的那一组存入状态而舍弃更矮的那一组。如前三个积木3,5,7,两组状态的塔分别为:3,5和5,7时,我们只存f[3][2] = 5,对以后的状态转移无错误影响。
决策:
第i个水晶放不放,放在哪,放完之后两塔的高低关系是否发生改变。
转移(注意逆向思维):
第i个水晶不放,f[i][j] = f[i - 1][j]
第i个水晶放上之后是矮塔,之前肯定也是矮塔,之前高度差更大,f[i][j] = f[i - 1][j + h[i]] + h[i]
第i个水晶放上之后是高塔,之前也是高塔,之前高度差要小一些,矮塔高度不变,f[i][j] = f[i - 1][j - h[i]]
第i个水晶放上之后是高塔,之前是矮塔,原高塔是现矮塔,原高塔高度为原矮塔高度加高度差,f[i][j] =f[i - 1][h[i] - j] + h[i] - j
方程:
f[i][j] = max(f[i - 1][j], f[i - 1][j + h[i]] + h[i], f[i - 1][j - h[i]], f[i - 1][h[i] - j] + h[i] - j)
code
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
using namespace std;
const int maxn = 55, maxh = 500010;
int h[maxn], n, ans;
int sum, f[maxn][maxh], now;
int main(){
scanf("%d", &n);
memset(f, -1, sizeof(f));
cin >> h[1];
f[1][0] = 0;
f[1][h[1]] = 0;
now = h[1];
for(int i = 2; i <= n; i++){
scanf("%d", &h[i]);
now += h[i];
for(int j = 0; j <= now; j++){
f[i][j] = f[i - 1][j];
// cout << j << " ";
if(f[i - 1][j + h[i]] != -1 && f[i - 1][j + h[i]] + h[i] <= now / 2){
// cout << "first " ;
f[i][j] = max(f[i][j], f[i - 1][j + h[i]] + h[i]);// 矮-矮
}
if(j >= h[i] && f[i - 1][j - h[i]] != -1 && f[i - 1][j - h[i]] <= now / 2){
// cout << "second " ;
f[i][j] = max(f[i][j], f[i - 1][j - h[i]]);//高-高
}
if(h[i] - j > 0 && f[i - 1][h[i] - j] != -1 && f[i - 1][h[i] - j] + h[i] - j){
// cout << "third ";
f[i][j] = max(f[i][j], f[i - 1][h[i] - j] + h[i] - j);//高-矮
}
// cout << f[i][j] << endl;
}
}
ans = max(ans, f[n][0]);
if(ans != 0) cout << ans << endl;
else
cout << -1 << endl;
return 0;
}