poj3124 The Bookcase

链接

http://poj.org/problem?id=3124

题解

水平低啊,自己想不出来啊
要尽可能地利用题目里隐含地条件来解题,这道题其实暗含一些条件
首先,最高的书肯定所在的书架的高度就等于这本书的高度,也就是说第一层的高度已经确定了
其次,三层的宽度之和是定值,等于所有书的宽度和
也就是说,如果我枚举其中两层的宽度,第三层的宽度就算出来了,
那么我枚举第二层和第三层的宽度,第一层的宽度就知道了
假设第二层第三层的宽度分别为 j,k j , k ,那么第一层的宽度就等于 Wjk W − j − k W W 表示所有书的宽度之和,书架的宽度就等于三层宽度的最大值
要使宽度乘以高度和最小,就要使高度和最小,第一层的高度已知,所以就是使二三两层的高度之和最小
现在问题就成了,我知道了三层的宽度分别是多少,要求一种摆放方式使得第二第三层的高度和最小
首先想到的是:能不能贪心?仔细思考之后,发现这似乎无法贪心,而类似于一个背包问题,因为你要把每本书装进三个背包中的一个,直接贪心的话,在每一层的尾端可能无法处理
现在考虑前i1本书已经装好,假设我记录了 f[i][j][k] f [ i ] [ j ] [ k ] 表示已经装进了前 i i 本书,第二层宽度为j,第三层宽度为 k k ,二三两层的最小高度之和,那么考虑我装入第i本书,由于我不知道之前怎么装的,也就无法确定这本书和已经装入的书的大小关系,所以我无法确定这本书装进去之后该不该更新答案、怎么更新
那就自然想到排个序,按照高度递减给书排个序,这样如果之前一层书架里面有书,后来装的就不会对书架的高度造成影响
那么继续想转移,如果我想把它装进第一层,就是 f[i][j][k]=f[i1][j][k] f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k ]
如果装进第二层,就要分类讨论,如果之前这一层没有书,那么放进去的这本书就会决定这一层的高度,否则这层的高度就不变,第三层同理

for(i=1 to N)for(j=0 to W)for(k=0 to W)
    {
        f[i][j][k]=f[i-1][j][k];
        if(bk[i].w<j)f[i][j][k]=min(f[i][j][k],f[i-1][j-bk[i].w][k]);
        if(bk[i].w==j)f[i][j][k]=min(f[i][j][k],f[i-1][0][k]+bk[i].h);
        if(bk[i].w<k)f[i][j][k]=min(f[i][j][k],f[i-1][j][k-bk[i].w]);
        if(bk[i].w==k)f[i][j][k]=min(f[i][j][k],f[i-1][j][0]+bk[i].h);
    }

数组开不下,要省掉第一维
只这样还是过不了的,需要一点特技来优化
显然我们想要优化j和k的枚举范围,原先是枚举到所有书的宽度和 W W
但是实际上不可能有这样的方案,至少每层有一本书
我现在试图找到一个面积的上界,然后找一个高度的下界,两者相除找到一个宽度的上界
都知道正方形面积最大,这里也适用,因为书的宽度30,尽量平均一点放,可以让任意两行的宽度之差小于 60 60 (可以用假设法证明)
这样得到一个比实际宽度大一点的数 W3+60 W 3 + 60 ,高度假设都是最高的书的高度 H H ,那么这个面积上界就是S=H(W3+60)
然后找一个高度的极小值,我们希望这个极小值尽量大一点,假设最高的书高度为 H H ,通过原题的数据范围,发现高度不小于150,那就假设最矮的书都是 150 150 ,三层的高度之和的比较大的极小值就是 H+300 H + 300
因此构造出一个 j,k j , k 的上界 M=S/(H+300) M = S / ( H + 300 )
到这里就不超时了

思想提取

要善于利用隐含条件来去掉一维,以降低思维难度,比如某些和为定值

代码

//DP 
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 100
#define cl(x) memset(x,0,sizeof(x))
#define iinf 0x3f3f3f3f
#define fil(x) memset(x,iinf,sizeof(x))
using namespace std;
int N, f[2110][2110];
struct book
{
    int h, w;
}bk[maxn];
bool operator<(book b1, book b2){return b1.h>b2.h;}
void init()
{
    fil(f), cl(bk);
    int i;
    scanf("%d",&N);
    for(i=1;i<=N;i++)scanf("%d%d",&bk[i].h,&bk[i].w);
    sort(bk+1,bk+N+1);
}
int max(int a, int b, int c)
{
    return max(max(a,b),c);
}
void dp()
{
    int i, j, k, ans=iinf, W=0, M;
    for(i=1;i<=N;i++)W+=bk[i].w;
    M=(W/3.0+60)*(3*bk[1].h)/(2*bk[1].h);
    for(j=0;j<=M;j++)for(k=0;k<=M;k++)f[i][j]=iinf;
    f[0][0]=0;
    for(i=2;i<=N;i++)for(j=M;j>=0;j--)for(k=M;k>=0;k--)
    {
        if(bk[i].w<j)f[j][k]=min(f[j][k],f[j-bk[i].w][k]);
        if(bk[i].w==j)f[j][k]=min(f[j][k],f[0][k]+bk[i].h);
        if(bk[i].w<k)f[j][k]=min(f[j][k],f[j][k-bk[i].w]);
        if(bk[i].w==k)f[j][k]=min(f[j][k],f[j][0]+bk[i].h);
    }
    for(j=0;j<=M;j++)for(k=0;k<=M;k++)if(f[j][k]<iinf and j and k)ans=min(ans,max(j,k,W-j-k)*(f[j][k]+bk[1].h));
    printf("%d\n",ans);
}
int main()
{
    int T;
    for(scanf("%d",&T);T--;)init(), dp();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值