题意:
给你n(3<=n<=70)本书,每本书有一个高度h和宽度t,你要构造一个三层的书架,把所有书都放上,使书架的总高度*3层中的最大宽度最小。
思路:
书架有3层,每一层都有高度和宽度,状态里怎么表示呢。首先把书按照高度排序(从高到低),最高的放在第一层,以后再在第一层加书,第一层的高度也不会变了。
d[i][j][k]:安排完前 i 本书,第2层书的宽度为j,第3层书的宽度为k时,第2,3层书架的高度之和最小值。
这样整个书架的高度 = 第1层高度 + 某个状态值。
整个书架的宽度 = max( j , k , sumw[n] - j - k )。sumw[n]是前n本书的总宽度。
如果放入某一层之前,这一层是空,那么这一层的高度从0->hi。
若不为空,则放入的这本书对本层无影响(高度已经排序,后面的总比前面小),高度+0;
可以用一个 f 函数来表示这种增长,f( 0, h) = h,其余情况为0。
inline int f(int w, int h){
return w == 0 ? h : 0;
}
决策一共3中,将当前书放到第1 或 2 或 3层,用刷表法转移:
- 若放入第1层,用d( i , j , k ) 更新d( i+1, j ,k)。因为第2,3层高度不变。
- 若放入第2层,用d(i , j , k) + f( j, hi) 更新 d( i+1, j + wi, k )。
- 若放入第3层,用d(i , j , k) + f( k, hi) 更新 d( i+1, j, k + wi )。
优化:
4. j + k < 前i本书的宽度总和。
5. 若上一层宽度和 - 下一场宽度和 > 30(一本书的宽度最大值),那么这本书可以放到下一层,这样既不会增加整个书架的宽度,也不增加高度。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 1<<30;
const int maxn = 70;
const int maxw = 30;
struct Book{
int h,w;
bool operator < (const Book &rhs) const{
return h > rhs.h||(h == rhs.h&&w > rhs.w);
}
};
Book books[maxn];
int d[2][maxn*maxw][maxn*maxw]; // 滚动数组
int sumw[maxn];
inline int f(int w, int h){
return w == 0 ? h : 0;
}
inline void update(int& newd, int d) {
if(newd < 0 || d < newd) newd = d;
}
int main()
{
//freopen("in.txt","r",stdin);
int T; scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
//int sumw = 0;
for(int i = 0; i < n; ++i) {
scanf("%d%d",&books[i].h,&books[i].w);
//sumw += books[i].w;
}
sort(books, books + n);
sumw[0] = 0;
for(int i = 1; i <= n; ++i){
sumw[i] = sumw[i-1] + books[i-1].w;
}
//memset(d,-1,sizeof(d)); 太慢
d[0][0][0] = 0;
int t = 0;
for(int i = 0; i < n; ++i){
// 初始化
for(int j = 0; j <= sumw[i+1]; ++j)
for(int k = 0; k <= sumw[i+1] - j; ++k) d[t^1][j][k] = -1;
for(int j = 0; j <= sumw[i]; ++j){
for(int k = 0; k <= sumw[i]-j; ++k){
int w1 = sumw[i] - j - k;
if(d[t][j][k] >= 0&&(j <= w1+30||j <= k+30)){
update(d[t^1][j][k], d[t][j][k]); // 放在第1层
update(d[t^1][j+books[i].w][k], d[t][j][k] + f(j, books[i].h)); // 第2层
update(d[t^1][j][k+books[i].w], d[t][j][k] + f(k, books[i].h)); // 第3层
}
}
}
t ^= 1;
}
int h1 = books[0].h; // 最高的书放在第一层
int ans = INF;
for(int j = 1; j <= sumw[n]; ++j){ // 至少一本书,宽度不为0
for(int k = 1; k <= sumw[n]-j; ++k){
int w1 = sumw[n] - j - k;
if(d[t][j][k] >= 0&&(j <= w1+30||j <= k+30)){
int w = max(max(j,k), w1); // 最大宽度
int h = h1 + d[t][j][k]; // 总高度
ans = min(ans, w*h);
}
}
}
printf("%d\n",ans);
}
return 0;
}