UVa 12099 Bookcase
题目
◇题目传送门◆(由于UVa较慢,这里提供一份vjudge的链接)
◇题目传送门(vjudge)◆
题目大意
有 本书,第 本书有一个高度 和宽度 ,现要求构建一个三层的书架,你必须把所有书放在书架上。设三层高度(该层最高的书的高度)之和为 ,书架总宽度(即每层总宽度的最大值)为 ,要求 尽可能小。
思路
首先我们可以考虑将所有书按高度从大到小排序。不妨设最高的书在第1层,且第二层的高度大于等于第三层的高度。
则我们可以定义状态 为当前已经安排了 本书,第二层的宽度为 ,第三层的宽度为 时第二层和第三层的高度之和。
为什么不记录第一层的高度?因为最高的书在第一层,即这一层的书永远都不会比它更高了。
为什么不记录第一层的宽度?因为目前三层的书的总宽度为前 本书的总宽度,所以只要知道了第二、第三层的宽度,就知道了第一层的宽度。
另外,因为这些书已经从大到小排了序,所以,一旦三层都放了书,则书架的总高度就已经确定了。
综上,我们可以得到以下两点:
- 若只有第一、第二层放了书,则当且仅当往第三层放书 时,第三层的高度会变为 ;
- 若只有第一层放了书,则当且仅当第二层放书 时,第二层的高度会变为 。
所以我们采用刷表法进行转移。
将书 放在第一层:用 更新 ,因为第一层的高度不变。
将书 放在第二层:用 更新 。
将书 放在第三层:用 更新 。
其中: 当且仅当 时等于 ,否则等于 。
注意:由于状态数量很大( ),所以我们必须采用滚动数组进行递推。
正解代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn=70;
const int Maxw=30;
const int INF=0x3f3f3f3f;
struct Book {
int h,w;
bool operator < (const Book rhs) const {
return h>rhs.h||(h==rhs.h&&w>rhs.w);
}
}A[Maxn+5];
int N,sumw[Maxn+5];
int dp[2][Maxn*Maxw+5][Maxn*Maxw+5];
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;
}
void Prepare() {
sort(A+1,A+N+1);
sumw[1]=0;
for(int i=1;i<=N+1;i++)
sumw[i]=sumw[i-1]+A[i-1].w;
}
int main() {
#ifdef LOACL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T;
scanf("%d",&T);
while(T--) {
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%d %d",&A[i].h,&A[i].w);
Prepare();
int t=0;
for(int i=1;i<=N;i++) {
for(int j=0;j<=sumw[i+1];j++)
for(int k=0;k<=sumw[i+1]-j;k++)
dp[t^1][j][k]=-1;
//不要使用memset,它太慢了!
for(int j=0;j<=sumw[i];j++)
for(int k=0;k<=sumw[i]-j;k++)
if(dp[t][j][k]>=0) {
update(dp[t^1][j][k],dp[t][j][k]);
update(dp[t^1][j+A[i].w][k],dp[t][j][k]+f(j,A[i].h));
update(dp[t^1][j][k+A[i].w],dp[t][j][k]+f(k,A[i].h));
}
t^=1;
}
int ans=INF;
for(int j=1;j<=sumw[N+1];j++)
for(int k=1;k<=sumw[N+1]-j;k++)
if(dp[t][j][k]>=0) {
int w=max(max(j,k),sumw[N+1]-j-k);
int h=A[1].h+dp[t][j][k];
ans=min(ans,h*w);
}
printf("%d\n",ans);
}
return 0;
}