【题目链接】
ybt 1226:装箱问题
OpenJudge NOI 4.6 19:装箱问题
【题目考点】
1. 贪心
【解题思路】
该题说是三维立方体,实际上无论是包裹还是产品,高度都是h,因而不用考虑高度,这实际上是二维平面上的问题。
1. 贪心选择性质的证明
贪心选择:选择最大的可以装入该包裹的产品装入该包裹
- 证明:存在最优解包含第一次的贪心选择。即存在最优解,第一个包裹中包含最大产品。
假设所有最优解都不包含第一次的贪心选择,即第一个包裹C中不包含最大的产品x。
最大的产品x一定存在于某个包裹内,记那个包裹为N。我们可以将N中的所有产品和C中的所有产品完全交换。总包裹数量不变,第一个包裹C中存在产品x,这与假设相悖,假设不成立,原命题得证。
- 证明:前k步都做贪心选择,存在最优解包含第k+1步的贪心选择。
是否做贪心选择区别在于:当前拿到一个最大的可以装入当前包裹C的产品x,是将这个产品放在当前包裹C中,还是将其放在一个新包裹N中。贪心选择是将其放在当前包裹C中。
使用反证法:假设对所有最优解,在装第k+1个产品时,当前关注的包裹C可以装入的最大产品为x,此时不进行贪心选择,不选择x,接下来只会装入大小小于x的产品。也可以说,当前包裹C中,产品x的数量不会更多。
下面考虑在N中包括x的部分产品能否和C中的一些产品或空位交换,且两包裹仍能装得下。如果可以,那么说明第k+1步进行了贪心选择,选择了产品x,总包裹数量不变,也是最优解。假设不成立,原命题得证。
- 如果N中x的数量大于C中x的数量,且x在C中与N中都是最大的产品,那么可以将N与C中的所有产品整体交换。
x为4*4、5*5、6*6都满足这一情况。或C中3*3的个数比N中3*3的个数更少。
例:x为5*5,在第k+1步往C中装产品时,没有按照贪心选择将5*5的产品装入C。后来5*5的产品装入了N,C中又装了一些产品。将C与N的所有产品交换后,C中存在贪心选择,总包裹数仍然最少,说明存在最优解在第k+1步进行了贪心选择,假设不成立,原命题得证。
以下为N中x的数量小于等于C中x的数量的情况:- 如果x是1*1的产品,那么C当时可以放1*1的产品但不放,存在一些位置空着,C中一定有可以放下1*1的空位。可以完成交换。
- 如果x是2*2的产品,那么C当时可以放2*2的产品但不放,去放1*1的产品或空着。那么在C中一定可以选择出2*2的区域,其中可以是1*1的产品也可以是空位,与x进行交换。
- 如果x是3*3的产品,说明先前加入C的是若干个3*3的产品。
- 如果C中已经有3个3*3的产品,剩下3*3的位置,这部分位置的产品或空位都可以与x进行交换。
- 如果C中已经有2个3*3的产品,且剩余的空间中摆了至多3个2*2的产品。
N中有2个3*3产品与至多3个2*2产品,此时,把N中的1个3*3与C中的2个2*2与1个1*1交换,结果为C中有3个3*3与1个2*2,N中有1个3*3与5个2*2,这种交换是可行的。
同理,如果N中有1个3*3与至多5个2*2,用N中的1个3*3交换C中的2个2*2与1个1*1是可行的。
例:将N中的红色3*3产品x与C中的蓝色两个2*2产品与1个1*1产品进行交换。交换 后一些1*1产品位置发生变化。
- 如果C中已经有1个3*3产品,剩余空间摆了至多5个2*2的产品。N中有1个3*3产品与至多5个2*2的产品,用N中的1个3*3交换C中的2个2*2与1个1*1是可行的。
如果待交换的产品没有那么多,可以用空位代替。
例:将N中的红色3*3产品x与C中的蓝色两个2*2产品与1个1*1产品进行交换。交换 后一些1*1产品位置发生变化。
- x不可能是4*4或更大的产品,如果是,C中可以放x但不放,C中的x只能是0个,而N中的x有1个,这是前面已经讨论过的“如果N中x的数量大于C中x的数量”的情况。
因此N中包括x的部分产品总能和C中的一些产品或空位交换位置。说明第k+1步进行了贪心选择,选择了产品x,总包裹数量不变,也是最优解。因而假设不成立,原命题得证。
2. 具体做法
考虑各种情况下,剩下的空间最多可以放下几个各种产品,总结成表格
已有 | 最多放几个3*3 | 最多放几个2*2 | 最多放几个1*1 |
---|---|---|---|
1个6*6 | 0 | 0 | 0 |
1个5*5 | 0 | 0 | 11 |
1个4*4 | 0 | 5 | 20 |
1个3*3 | 3 | 5 | 27 |
2个3*3 | 2 | 3 | 18 |
3个3*3 | 1 | 1 | 9 |
无 | 4 | 9 | 36 |
此这基础上,每多放一个2*2,1*1可放个数减4。
观察规律,1*1可以放的个数就是总面积6*6减去所有放入其中的产品的总面积。
在已有3*3的情况下,每多放入一个3*3,2*2可以放入的个数减2。
这里只需要设数组记录放入1个某大小产品后,其他大小产品可以放入的数量。
从大到小遍历各个产品,找到一个产品,开一个新包裹将其放入。如果剩余位置还可以放产品,那么放入对应产品,改变剩余位置可放产品的数量。直到没有位置可放或没有足够的产品可放为止。再从大到小找下一个产品,开一个新包裹将其放入。重复上述过程,直到看完所有产品为止。
【题解代码】
解法1:贪心(本人原创)
#include <bits/stdc++.h>
using namespace std;
int status[8][4];//status[i][j]:包裹中已有1个大小为i*i的产品,此时可以最多放入多少个j*j的产品
void initStatus()
{//status[0]:包裹中没有产品时,各种产品最多可以放多少个
status[2][2] = 8;
status[3][2] = 5, status[3][3] = 3;
status[4][2] = 5;
for(int i = 1; i <= 6; ++i)
status[i][1] = 36 - i*i;
}
int pro[8];//pro[i]:i*i产品的数量
int main()
{
initStatus();
while(true)
{
int sum = 0, bag = 0, rem[8];//sum:总产品数量 bag:包裹数 rem[i]:当前剩余空间能放几个i*i
for(int i = 1; i <= 6; ++i)
{
cin >> pro[i];
sum += pro[i];
}
if(sum == 0)//如果6个数都是0,那么加和为0,要跳出循环
break;
for(int i = 6; i >= 1; --i)
{
while(pro[i] > 0)
{
bag++;//用一个新包裹放i*i
pro[i]--;
for(int j = 1; j <= 3; ++j)//获取当前剩余空间情况
rem[j] = status[i][j];
int j = 3;
while(j >= 1)
{
if(rem[j] > 0 && pro[j] > 0)//如果可以放j*j的产品
{
pro[j]--;//放一个产品
if(j == 3)
{
rem[3]--;
rem[2] -= 2;
rem[1] -= 9;
}
else if(j == 2)
{
rem[2]--;
rem[1] -= 4;
}
else//j == 1
rem[1]--;
}
else
--j;
}
}
}
cout << bag << endl;
}
return 0;
}
解法2:手动完成贪心过程
思路来自zqhf123博客:zqhf123 1226:装箱问题
由于箱子种类不多,我们可以手动完成每一步的贪心行为。先装大产品,看占了多少空间,剩下多少空间。再看小产品。逐次算出当前可以给下一个产品留出的空位。
#include<bits/stdc++.h>
using namespace std;
int bag;//总包裹数
int k[4] = {0, 5, 3, 1};//k[i]表示3*3的产品有i个时(k[0]表示有4个时)剩余空间可以放2*2产品的个数
int pro[8], p2, p1;//p2:2*2空位数 p1:1*1空位数
int main()
{
while(true)
{
for(int i = 1; i <= 6; ++i)
cin >> pro[i];
if(pro[1] == 0 && pro[2] == 0 && pro[3] == 0 && pro[4] == 0 && pro[5] == 0 && pro[6] == 0)
break;
bag = pro[6] + pro[5] + pro[4] + ceil(pro[3]/4.0);//6*6和5*5和4*4一定是一个占一个箱子,而3*3 4个占一个箱子
p2 = 5*pro[4]+k[pro[3]%4];//k[pro[3]%4]:有pro[3]%4个3*3在一个包裹中时,剩下的位置可以放2*2的数目
if(pro[2] > p2)//2*2的个数比我们留出来为2*2的空间个数多,就需要为2*2另开箱子
bag += ceil((pro[2]-p2)/9.0);//多出来的2*2箱子应该占用的新箱子数
p1 = 36*bag-36*pro[6]-25*pro[5]-16*pro[4]-9*pro[3]-4*pro[2];//求出当前包裹中所有剩下的可以放1*1的位置数
if(pro[1] > p1)//如果1*1的个数,比我们留出的空间多就需要另开空间
bag += ceil((pro[1]-p1)/36.0);//将多出来的另开箱子
cout << bag << endl;
}
return 0;
}