问题
参加链接,有n本书, ( 3 < = n < = 70 ) (3<=n<=70) (3<=n<=70),每本书有高度 H i H_i Hi,厚度 W i W_i Wi,其中 150 < = H i < = 300 150<=H_i<=300 150<=Hi<=300, 5 < = W i < = 30 5<=W_i<=30 5<=Wi<=30,每本书任意放在书架的一层,书架共有三层(每层非空),书架的总宽度为三层中最大宽度 w w w,高度 h h h为三层的高度之和,要求 h ∗ w h*w h∗w尽量小
分析
这道题的要点在于问题的分析和化简,对于复杂的问题来说,分析问题,简化问题是最重要的
1.对于n本书来说,他们放进书架中的先后顺序对于结果无影响,所以可以用一个维度表示前i本书已经放入书架中
2.对于宽度的处理,三层书架的书籍宽度,只要记录其中的两层就可以了,第三层可以用减法算出来
3.高度的确定,三层书籍中的高度很不好处理,因为他们取决于每层最高的那一本书,如果继续添加维度记录高度,状态就太多了,所以要简化高度的记录,方便处理高度相关的状态变化
首先对于n本书按照高度从高到低进行排序,先放进书架的一定是高的书籍,所以每层一旦有书籍,那么这一层的高度就确定了,然后假设最高的书籍一定放在第1层(放在哪层不影响最后的结果),那么第1层的高度就确定了,然后第2,3层的高度只记录高度之和(计算时不用看每层的高度,只看总和),每层是否有书籍可以通过宽度来确定
于是可以用
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]来确定状态,i代表前i本书,j代表第二层的宽度,k代表第三层的宽度,
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示对应的第2,3层高度之和最小值,通过这些数据可以计算
h
∗
w
h*w
h∗w
现在的状态总数是
70
×
2100
×
2100
70\times2100\times2100
70×2100×2100,占用空间太大了,开不下,所以可以使用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 第i+1本书在第1层,dp[i+1][j][k]=dp[i][j][k];在第2层,dp[i+1][j+wi+1][k]=f(j,Hi+1)+dp[i][j][k]。其中,f(0,h)=h,其他情况f=0;第3层,dp[i+1][j][k+wi+1]=f(k,Hi+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
继续优化:根据紫书上的解释,利用题目本身的特性,继续优化时间
-
w
w
2
+
w
w
3
<
t
o
t
a
l
w
i
d
t
h
ww_2+ww_3<totalwidth
ww2+ww3<totalwidth,也就是说第2,3两层的宽度之和小于总宽度totalwidh
2.假设 w w 1 , w w 2 , w w 3 ww_1,ww_2,ww_3 ww1,ww2,ww3分别是第1, 2,3层的宽度, 对于1,2层来说,如果 w w 2 − w w 1 > 30 ww_2-ww_1>30 ww2−ww1>30,那么就可以移动第2层一本书到第1层,不会是高度变大,同时能减少宽度,同理 w w 3 − w w 2 < = 30 ww_3-ww_2<=30 ww3−ww2<=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 ww3<=(totalwidth+90)/3,ww2<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