问题
参加链接,有n本书, ,每本书有高度 ,厚度 ,其中 , ,每本书任意放在书架的一层,书架共有三层(每层非空),书架的总宽度为三层中最大宽度 ,高度 为三层的高度之和,要求 尽量小
分析
这道题的要点在于问题的分析和化简,对于复杂的问题来说,分析问题,简化问题是最重要的
1.对于n本书来说,他们放进书架中的先后顺序对于结果无影响,所以可以用一个维度表示前i本书已经放入书架中
2.对于宽度的处理,三层书架的书籍宽度,只要记录其中的两层就可以了,第三层可以用减法算出来
3.高度的确定,三层书籍中的高度很不好处理,因为他们取决于每层最高的那一本书,如果继续添加维度记录高度,状态就太多了,所以要简化高度的记录,方便处理高度相关的状态变化
首先对于n本书按照高度从高到低进行排序,先放进书架的一定是高的书籍,所以每层一旦有书籍,那么这一层的高度就确定了,然后假设最高的书籍一定放在第1层(放在哪层不影响最后的结果),那么第1层的高度就确定了,然后第2,3层的高度只记录高度之和(计算时不用看每层的高度,只看总和),每层是否有书籍可以通过宽度来确定
于是可以用
来确定状态,i代表前i本书,j代表第二层的宽度,k代表第三层的宽度,
表示对应的第2,3层高度之和最小值,通过这些数据可以计算
现在的状态总数是
,占用空间太大了,开不下,所以可以使用0-1背包中的滚动数组节省空间
转移方程:
#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
继续优化:根据紫书上的解释,利用题目本身的特性,继续优化时间
-
,也就是说第2,3两层的宽度之和小于总宽度totalwidh
2.假设 分别是第1, 2,3层的宽度, 对于1,2层来说,如果 ,那么就可以移动第2层一本书到第1层,不会是高度变大,同时能减少宽度,同理 , 所以
最后是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