http://acm.hdu.edu.cn/showproblem.php?pid=3127
题意:
现在有一块X*Y的矩形布条, 然后有n种规格的x[i]*y[i]的小布条, 每种布条可以卖出val[i]的价值. 问你原始的X*Y布条最多能卖多少价值? 其中每次切割布条只能水平或垂直的切, 且一刀到底.
分析:
本题看起来比较麻烦, 但是搞懂原理后还是很简单的. 把当前还剩余的矩形布条看成容量, 那么我们就是再这有限的容量里面要找出最大价值的布条总和来.
令dp[i][j]==x 表示当前长i和宽j的布条能切割出的最大价值为x.
这里有个结论要注意: 我们每次从大矩形中切割出一个小矩形, 总是沿着大矩形的顶角边缘切割将不会丢失最优解.
比如一个i*j的大矩形, 我们如果要从中切割出一个x*y的小矩形, 有下面4种方式(大矩形可以选择先下垂直那刀或先下水平那刀,
小矩形能旋转):
(仔细体会上面这个图.)
有了上面的图, 下面我们的递推公式就出来了:
if(i>=r[k].x&& j>=r[k].y)
dp[i][j]=max(dp[i][j] , max( dp[i-r[k].x][j]+dp[r[k].x][j-r[k].y] ,dp[i-r[k].x][r[k].y]+dp[i][j-r[k].y] )+r[k].val );
if(i>=r[k].y && j>=r[k].x)
dp[i][j]=max(dp[i][j] , max( dp[r[k].y][j-r[k].x]+dp[i-r[k].y][j] ,dp[i-r[k].y][r[k].x]+dp[i][j-r[k].x] )+r[k].val );
上面两个公式好像看起来很复杂, 其实理解上面的图之后就很简单了. 本质就是:
原始矩形能获得的最大价值 == max(小矩形价值 + 被水平一刀和竖直一刀切割后剩下的两个矩形能获得的最大价值和 )
也就是求切割后的3个矩形的价值和, 看看哪种方式切割剩下的矩形价值和最大.(两刀之后,
原始矩形必然变成3个新矩形)
初始化: dp为全0.
最终所求: dp[X][Y].
注意:
完全背包问题限制条件的维度j和物品编号的维度i的循环先后顺序是可以互换的. 但是此题必须先循环X和Y的维度,
然后才是物品编号的维度. 因为一般的完全背包问题的最优解对于物品的选取顺序没有要求, 可以先区任何物品. 但是此题对于原始矩形来说, 你在它的顶角边缘先切割那个小矩形是明显不同的,有可能你先切割1号矩形就得不到最优解, 但是你先切割3号矩形才能得到最优解(仔细想想是不是).
AC代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1000+5; int X,Y;//原始矩形宽和高 int n; //有多少个小布条 struct Node//每个小布条 { int x,y; int val; }r[10+5]; int dp[maxn][maxn]; int main() { int T; scanf("%d",&T); while(T--) { //读取输入+初始化 scanf("%d%d%d",&n,&X,&Y); for(int i=1;i<=n;i++) scanf("%d%d%d",&r[i].x,&r[i].y,&r[i].val); memset(dp,0,sizeof(dp)); //递推,注意:先X和Y,然后才是矩形编号. //如果先循环矩形编号,就错了. for(int i=0;i<=X;i++) for(int j=0;j<=Y;j++) for(int k=1;k<=n;k++) { if(i>=r[k].x && j>=r[k].y) dp[i][j]=max( dp[i][j] , max( dp[i-r[k].x][j]+dp[r[k].x][j-r[k].y] , dp[i-r[k].x][r[k].y]+dp[i][j-r[k].y] )+r[k].val ); if(i>=r[k].y && j>=r[k].x) dp[i][j]=max( dp[i][j] , max( dp[r[k].y][j-r[k].x]+dp[i-r[k].y][j] , dp[i-r[k].y][r[k].x]+dp[i][j-r[k].x] )+r[k].val ); } //输出结果 printf("%d\n",dp[X][Y]); } }