【UVa】【DP】12099 Bookcase

本文详细解析UVa12099Bookcase问题,介绍了一种利用动态规划解决三维书架布局优化的方法。通过合理分配书籍在三层书架上的位置,以最小化书架总体积。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UVa 12099 Bookcase

题目

◇题目传送门◆(由于UVa较慢,这里提供一份vjudge的链接)
◇题目传送门(vjudge)◆

题目大意

N N 本书,第i本书有一个高度 Hi H i 和宽度 Wi W i ,现要求构建一个三层的书架,你必须把所有书放在书架上。设三层高度(该层最高的书的高度)之和为 h h ,书架总宽度(即每层总宽度的最大值)为w,要求 h×w h × w 尽可能小。

思路

首先我们可以考虑将所有书按高度从大到小排序。不妨设最高的书在第1层,且第二层的高度大于等于第三层的高度。

则我们可以定义状态 d(i,j,k) d ( i , j , k ) 为当前已经安排了 i i 本书,第二层的宽度为j,第三层的宽度为 k k 时第二层和第三层的高度之和。

为什么不记录第一层的高度?因为最高的书在第一层,即这一层的书永远都不会比它更高了。

为什么不记录第一层的宽度?因为目前三层的书的总宽度为前i本书的总宽度,所以只要知道了第二、第三层的宽度,就知道了第一层的宽度。

另外,因为这些书已经从大到小排了序,所以,一旦三层都放了书,则书架的总高度就已经确定了。

综上,我们可以得到以下两点:

  • 若只有第一、第二层放了书,则当且仅当往第三层放书 i i 时,第三层的高度会变为Hi
  • 若只有第一层放了书,则当且仅当第二层放书 i i 时,第二层的高度会变为Hi

所以我们采用刷表法进行转移。

将书 i i 放在第一层:用d(i,j,k)更新 d(i+1,j,k) d ( i + 1 , j , k ) ,因为第一层的高度不变。

将书 i i 放在第二层:用d(i,j,k)+f(j,Hi)更新 d(i+1,j+Wi,k) d ( i + 1 , j + W i , k )

将书 i i 放在第三层:用d(i,j,k)+f(k,Hi)更新 d(i+1,j,k+Wi) d ( i + 1 , j , k + W i )

其中: f(j,Hi) f ( j , H i ) 当且仅当 j=0 j = 0 时等于 Hi H i ,否则等于 0 0

注意:由于状态数量很大(70×2100×2100),所以我们必须采用滚动数组进行递推。

正解代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值