题意,n(<=70)本书,每本书有一个高度(<=150),宽度(<=30),把他们分到书架里的三层,每层的高度就是这层里所有书的最大高度,宽度就是所有书的宽度和。ans=三层高度和*三层宽度最大值,求ans最小值
又是一道ACM毒瘤好dp,最近做dp感觉出来了点规律,很多时候可以制定一些规则,使无论怎样都有最优答案都能满足这些规则,这样减少了最优答案的数量dp自然就简化了。所以在这个题中,如果求出来最优方案后,调整每一层的顺序不会改变答案,所以就定第一层高度最高,第二层其次。因为第一本书肯定要放进书架里,所以先把它放进第一层。
设状态的时候,设dp(i,j,k)为放了i本书,第二层宽度为j,第三层宽度为j,二三层高度和的最小值。这样根据j,k可以推出来第一层宽度。所以每次对于第i本书有放进三层三种决策,转移方程推一下就可以了。
这样会发现,如果把所有书放到一层,那么这一层宽度最大30*70=2100,空间用滚动数组优化,时间不优化是70*2100*2100,大数据会被卡(事实上vjudge上数据不强,最多20组测试数据跑了1180ms)
书上介绍了两个神仙优化,看完了以后我第一次知道dp还可以剪枝
1.这个还比较好想,放第i本书时j+k应小于第2本书到第i本书宽度和,这样可以在循环k时剪去一部分无用状态
2.所有书宽度和为sum,设想如果相邻两层宽度差>30,那么把一本书从这层拿到邻层,高度和不加,宽度也不加,所以我们只保留状态w1+30>=w2,w2+30>=w3,化式子可得w2<=(sum+30)/2,w3<=(sum+60)/3,这样w2最大1065,w3最大720常数小了六倍左右
这两个优化一上,常数小了六倍多,跑了180ms
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define INF (2100010) #define LL long long using namespace std; int dp[2][1100][800],n,A[200]; struct Book{ int w,h; void Read(){ scanf("%d %d",&h,&w); } bool operator < (const Book &a) const{ return h>a.h; } }T[200]; LL Min(LL a,LL b){ if (a<b) return a; return b; } void Work(){ int i,j,k,sum=0,max2,max3; scanf("%d",&n); memset(A,0,sizeof(A)); for (i=1;i<=n;i++) T[i].Read(); sort(T+1,T+n+1); for (i=2;i<=n;i++) A[i]=T[i].w+A[i-1]; sum=A[n]+T[1].w; memset(dp,127,sizeof(dp)); max2=(sum+30)/2+10; max3=(sum+60)/3+10; dp[1][0][0]=0; for (i=2;i<=n;i++) for (j=0;j<=max2;j++) for (k=0;k+j<=A[i],k<=max3;k++){ int &ans=dp[i&1][j][k]; ans=INF; ans=min(ans,dp[(i-1)&1][j][k]);//把书放在第一层 if (j-T[i].w>=0)//把书放在第二层 ans=min(ans,dp[(i-1)&1][j-T[i].w][k]+T[i].h*(j==T[i].w)); if (k-T[i].w>=0)//把书放在第三层 ans=min(ans,dp[(i-1)&1][j][k-T[i].w]+T[i].h*(k==T[i].w)); } LL ans=210000000000000000; for (i=1;i<=max2;i++) for (j=1;j<=max3;j++) ans=Min(ans,(LL)max(max(i,j),sum-i-j)*(LL)(dp[n&1][i][j]+T[1].h)); cout<<ans<<endl; } int main(){ int Case_Num; scanf("%d",&Case_Num); while (Case_Num--) Work(); return 0; }