UVA - 12099 The Bookcase --类似于0-1背包的状态优化

问题

参加链接,有n本书, ( 3 < = n < = 70 ) (3<=n<=70) ,每本书有高度 H i H_i ,厚度 W i W_i ,其中 150 < = H i < = 300 150<=H_i<=300 , 5 < = W i < = 30 5<=W_i<=30 ,每本书任意放在书架的一层,书架共有三层(每层非空),书架的总宽度为三层中最大宽度 w w ,高度 h h 为三层的高度之和,要求 h w h*w 尽量小

分析

这道题的要点在于问题的分析和化简,对于复杂的问题来说,分析问题,简化问题是最重要的
1.对于n本书来说,他们放进书架中的先后顺序对于结果无影响,所以可以用一个维度表示前i本书已经放入书架中

2.对于宽度的处理,三层书架的书籍宽度,只要记录其中的两层就可以了,第三层可以用减法算出来

3.高度的确定,三层书籍中的高度很不好处理,因为他们取决于每层最高的那一本书,如果继续添加维度记录高度,状态就太多了,所以要简化高度的记录,方便处理高度相关的状态变化

首先对于n本书按照高度从高到低进行排序,先放进书架的一定是高的书籍,所以每层一旦有书籍,那么这一层的高度就确定了,然后假设最高的书籍一定放在第1层(放在哪层不影响最后的结果),那么第1层的高度就确定了,然后第2,3层的高度只记录高度之和(计算时不用看每层的高度,只看总和),每层是否有书籍可以通过宽度来确定

于是可以用 d p [ i ] [ j ] [ k ] dp[i][j][k] 来确定状态,i代表前i本书,j代表第二层的宽度,k代表第三层的宽度, d p [ i ] [ j ] [ k ] dp[i][j][k] 表示对应的第2,3层高度之和最小值,通过这些数据可以计算 h w h*w
现在的状态总数是 70 × 2100 × 2100 70\times2100\times2100 ,占用空间太大了,开不下,所以可以使用0-1背包中的滚动数组节省空间

转移方程: i + 1 1 d p [ i + 1 ] [ j ] [ k ] = d p [ i ] [ j ] [ k ] 2 d p [ i + 1 ] [ j + w i + 1 ] [ k ] = f ( j , H i + 1 ) + d p [ i ] [ j ] [ k ] f ( 0 , h ) = h , f = 0 3 d p [ i + 1 ] [ j ] [ k + w i + 1 ] = f ( k , H i + 1 ) + d p [ i ] [ j ] [ k ] f ( 0 , h ) = h , f = 0 第i+1本书在第1层,dp[i+1][j][k]=dp[i][j][k];在第2层,dp[i+1][j+w_{i+1}][k]=f(j,H_{i+1})+dp[i][j][k] 。其中,f(0,h)=h,其他情况f=0;第3层,dp[i+1][j][k+w_{i+1}]=f(k,H_{i+1})+dp[i][j][k] 。其中,f(0,h)=h,其他情况f=0

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <utility>
using namespace std;
const int maxn=70,maxh=300,maxw=30,maxwidth=2102;
struct Book{
    int h,w;
    bool operator < (const Book &rhs) const {
        return h>rhs.h;
    }
}books[maxn];
//width是宽度累加的值,dp[j][k],j是第二层宽度之和,k是第三层宽度之和,dp[j][k]是2,3层高度之和
//第1层高度是books[0].h
int kase,n,width[maxn],dp[maxwidth][maxwidth],totalwidth;

inline int f(int a,int b){
    if(a==0)  return b;
    return 0;
}

inline void update(int &x,int y){
    if(x==-1 || x>y) x=y;
}

int main(void){
    cin>>kase;
    while(kase--){
        cin>>n;
        for(int i=0;i<n;++i)
            cin>>books[i].h>>books[i].w;
        sort(books,books+n);
        width[0]=books[0].w;
        for(int i=1;i<n;++i) width[i]=width[i-1]+books[i].w;
        totalwidth=width[n-1];
        //使用滚动数组更新状态,i代表0-i本书
        for(int i=0;i<=totalwidth;++i) memset(dp[i],-1,(totalwidth+1)*sizeof(int));
        dp[0][0]=0;
        for(int i=1;i<n;++i){
            for(int j=totalwidth;j>=0;--j){
                for(int k=totalwidth;k>=0;--k){
                    if(dp[j][k]==-1) continue;
                    update(dp[j+books[i].w][k],dp[j][k]+f(j,books[i].h));
                    update(dp[j][k+books[i].w],dp[j][k]+f(k,books[i].h));
                }
            }
        }
        int ans=-1;
        //非空
        for(int j=1;j<=totalwidth;++j){
            for(int k=1;k<=totalwidth;++k){
                if(dp[j][k]==-1) continue;
                update(ans,max(max(j,k),totalwidth-j-k)*(books[0].h+dp[j][k]));
            }
        }
        printf("%d\n",ans);
    }
}
//time:  200ms

继续优化:根据紫书上的解释,利用题目本身的特性,继续优化时间

  1. w w 2 + w w 3 < t o t a l w i d t h ww_2+ww_3<totalwidth ,也就是说第2,3两层的宽度之和小于总宽度totalwidh
    2.假设 w w 1 , w w 2 , w w 3 ww_1,ww_2,ww_3 分别是第1, 2,3层的宽度, 对于1,2层来说,如果 w w 2 w w 1 > 30 ww_2-ww_1>30 ,那么就可以移动第2层一本书到第1层,不会是高度变大,同时能减少宽度,同理 w w 3 w w 2 < = 30 ww_3-ww_2<=30 , 所以 w w 3 < = ( t o t a l w i d t h + 90 ) / 3 , w w 2 < t o t a l w i d t h / 2 ww_3<=(totalwidth+90)/3,ww_2<totalwidth/2
    最后是50ms
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <utility>
using namespace std;
const int maxn=70,maxh=300,maxw=30,maxwidth=2102;
struct Book{
    int h,w;
    bool operator < (const Book &rhs) const {
        return h>rhs.h;
    }
}books[maxn];
//width是宽度累加的值,dp[j][k],j是第二层宽度之和,k是第三层宽度之和,dp[j][k]是2,3层高度之和
//第1层高度是books[0].h
int kase,n,width[maxn],dp[maxwidth][maxwidth],totalwidth;

inline int f(int a,int b){
    if(a==0)  return b;
    return 0;
}

inline void update(int &x,int y){
    if(x==-1 || x>y) x=y;
}

int main(void){
    cin>>kase;
    while(kase--){
        cin>>n;
        for(int i=0;i<n;++i)
            cin>>books[i].h>>books[i].w;
        sort(books,books+n);
        width[0]=books[0].w;
        for(int i=1;i<n;++i) width[i]=width[i-1]+books[i].w;
        totalwidth=width[n-1];
        //使用滚动数组更新状态,i代表0-i本书
        for(int i=0;i<=totalwidth;++i) memset(dp[i],-1,(totalwidth+1)*sizeof(int));
        dp[0][0]=0;
        int rj=totalwidth/2;
        int rk=min((totalwidth+90)/3,totalwidth);
        for(int i=1;i<n;++i){
            for(int j=rj;j>=0;--j){
                for(int k=min(rk,totalwidth-j-1);k>=0;--k){
                    if(dp[j][k]==-1) continue;
                    update(dp[j+books[i].w][k],dp[j][k]+f(j,books[i].h));
                    update(dp[j][k+books[i].w],dp[j][k]+f(k,books[i].h));
                }
            }
        }
        int ans=-1;
        //非空
        for(int j=1;j<=rj;++j){
            for(int k=1;k<=rk;++k){
                if(j+k>=totalwidth) break;
                if(dp[j][k]==-1) continue;
                update(ans,max(max(j,k),totalwidth-j-k)*(books[0].h+dp[j][k]));
            }
        }
        printf("%d\n",ans);
    }
}
//50ms
发布了15 篇原创文章 · 获赞 0 · 访问量 172

猜你喜欢

转载自blog.csdn.net/zpf1998/article/details/103957883
今日推荐