积木搭建解题报告

本文介绍了一道关于单位立方体积木构形的问题,包括积木的定义、分类,以及如何用最少数量的积木搭建指定构形的算法分析,涉及到深度优先搜索、状态生成和优化方法。
摘要由CSDN通过智能技术生成

[问题描述]
单位立方体(unit cube)是一个1×1×1的立方体,它的所有角的x,y和z坐标都是整数。我们称两个单位立方体是连通的,当且仅当他们有一个公共的面;一个构形(solid)或者是一个单位立方体,或者是由多个单位立方体组成的连通物体(如图一所示),它的体积就是它所含的单位立方体的个数。
一块积木(block)是一个体积不超过4的构形。如果两块积木可以平移、旋转(注意,不能成镜像)操作变得完全一样,我们就称它们属于同一类。这样,积木总共有12类(如图2所示)。图2中积木的不同颜色仅仅是为了帮助你看清楚它的结构,并没有其他的含义。
图二
一个构形可以由若干块积木组成。如果用一些积木可以互不重叠的搭建出某个构形,那么就称这些积木是这个构形的一种分解(decomposition)。
你的任务是写一个程序,对于给定的一组积木种类和一个构形S,求出境可能少的积木个数,使得他们能够搭建出构形S。你只需给出这个最少的积木个数,以及这些积木各自所属的种类,而不必给出具体的搭建方案。
[输入输出格式]
(1)输入:
在输入文件中,一个单位立方体用同一行中的三个整数x,y,z表示。坐标(x,y,z)是该单位立方体各个顶点坐标{(xi,yi,zi)|i=1..8}中xi+yi+zi最小的。
输入文件有两个。第一个输入文件描述了积木的不同种类,文件为types.in。在下问“输入输出样例”部分给出了一个types.in文件,他一次定义了图2所示的12中积木。注意:所有的测试数据都将统一采用该types.in文件。
每一种类的积木由连续的若干行描述。第一行市积木的种类编号I(1≤I≤12);第二行是该类积木的体积V(1≤V≤4);随后的V行每行包含三个整数x,y和z,表示组成该类积木的一个单位立方体,其中1≤x,y,z≤4。
(2)输出:
输出的文件名为:block.out。
它的第一行是一个正整数M,表示使用积木的最少块数。
文件的第二行列出M个整数,表示搭建出构形S的M块积木各自的种类。

[输入输出样例]
输入输出样例

[数据结构]
为描述清楚,先列出数据结构:

TPoint = record  {空间中的一个单位立方体}
  x,y,z: Shortint;
end;
Tar: array[1..50] of TPoint; {目标形状}
TCube = array[1..4] of TPoint; {一个积木}
Cube: array[1..most] of TCube; {所有的状态}

[算法分析]
看到这个题目,就会想到搜索。这是一道有一定难度的题目,盲目的搜索是不行的,因为题目中说明了每种积木都可以经过翻转和平移,然而,只要得到了平移或翻转后的积木,我们就只需要一个一个的往构形里面填,直到填满,这显然是一个深度优先搜索。然而对于这道题目来说,除了搜索之外,还有一个难点,就是生成所有的翻转或平移后的积木状态。
我们考虑用一个三员组表示空间中的1*1*1的立方体(单位立方体),三员组(x,y,z)表示空间(x,y,z)处有一个单位立方体。这样我们就有三种数据结构:

TPoint = record{一个点}
    x,y,z: Shortint;
end;
TCube = array[1..4] of TPoint;{一个积木}
TBlock = array[1..50] of TPoint;{给定的三维形状}

右图所示图形可表示为:右图
(0,0,0) (0,0,1) (0,1,1)

怎样生成所有状态:

首先,题目中指出:可以平移,翻转,但不可以成镜像。如何处理平移?我们只要保存一个积木中四个顶点的相对位置,在搜索的时候自然就可以完成平移,在此我们规定一个积木的一个单位立方体为(0,0,0),则其余三个可以用相对于这个单位立方体的位置来表示。如何处理翻转?由于题目规定不可以成镜像,所以,我们在生成所有状态时的翻转过程中,必成偶数次镜像(因为:成偶数次镜像等于没成镜像)。由于旋转积木很难理解,也有可能会遗漏一些情况,我们采用的是旋转坐标轴的方法。
可以这么理解旋转坐标轴法:
顾名思义,就是要在三维空间中,将坐标轴旋转,当然,旋转后的坐标要两两垂直,在满足这个条件的情况下,用temp:TCube表示某积木cube Tcube经过某种旋转后所得的状态。设func(temp)表示有几条坐标线(射线)所在直线和原来这条坐标线所在直线的不重合的。考虑func(temp)的不同值:设f(temp)表示在func(temp)的基础上得到的表示temp性质的函数:
这里写图片描述
对于某一条特定的坐标,如果规定一个方向为正方向,则可以用g(temp)表示旋转后的新坐标线中方向和原来方向不同的条数。(注:这里的方向并非表示三维空间中的方向,只表示正负)。易得:若f(temp)+g(temp)为偶数,则temp是符合题意,即成偶数次镜像的。
搜索的时候,另需一个数组space:array[1..7,1..7,1..7] of byte;
space[x,y,z]=1表示此处未放, space[x,y,z]=0表示放了。

procedure search(u,dep,left: integer);
var
  i: integer;
begin
  if left = 0 then 
  begin 
    修改;退出;
  end;
  若(left – 1) DIV 4 + 1 >= min then Exit;{分支定界}
  找到一个space [tar[u,1],tar[u,2],tar[u,3]]<>0的u;{tar是目标形状}
  若不存在则退出;
  repeat
    找到一个可以放置且覆盖(tar[u,1],tar[u,2],tar[u,3])的积木;
    若找到则:
    begin
      放下;
      search(u+1,dep+1,left-count[i]);{count[i]是第I个积木的体积}
    end;
  until 找不到;
end;

值得注意的是, 找一个可放置的积木时,我们不仅要枚举积木,而且要枚举点(tar[u,1],tar[u,2],tar[u,3])在积木中的位置。如下面的图形:我们要枚举点(tar[u,1],tar[u,2],tar[u,3])在积木中的四中位置情况,然后递归搜索,这样才能够全面。

这里写图片描述
至此,这道题目可以说是得到比较圆满解决了,按照这种方法便出来的程序速度是比较快的,能够对一半左右的测试数据。

[优化方法]
这里讲的优化,主要是针对搜索时的一些技巧来讲的。

在搜索的过程中,我们发现:我们的目的实际上就是要把一些积木放到构形里面去,不妨换个角度,把一个构形看成由许多单位立方体构成的,同样的,一个积木也是由许多单位立方体构成。我们把这个构形拆开承单个的单位立方体,每次从里面去出成分和某一块积木陈分相同一些单位立方体。因此,不妨令这些单位立方体是有序的,同样,积木的描述也是有序的。搜索时,由小到大去填充构形的某一个单位立方体,如果轮到填充构形中的某一个单位立方体时,我们就必须让它成为某一个积木描述中最靠前(最”小”)的那一部分,因为如果不这样,这个单位立方体就再不可能被覆盖了。这样就免去了上面所说的确定这个单位立方体中相对位置的过程,效率有很大的提高。然而,我们需要一个标准来衡量一个单位立方体的“大小”,这很简单,就先以x为关键字排序;在x相同的前提下,以y为关键字排序;在x,y都相同的前提下,以z为关键字排序。

在程序开始时可以得到一个最优值best=(n – 1) DIV 4 + 1;一旦搜索到的解达到了这个值就可以中止程序。

因为各种积木的第一个单位立方体都是(0,0,0),保存各种状态时只需用三个TPoint,虽然这对于空间存储来说并不是问题。

此外,搜索的顺序对于时间效率很重要,在编程当中,我发现,若把较为平直的积木放在cube数组的前面,这样,循环时先找到的就是较平直的,事实证明,用这样的方法,很多的数据都有明显的超时,这可能是由于平直的积木不利于剪枝而造成的。我们可以同样考虑将积木按体积从达到小排序,这样,即容易出解也利于剪枝。

如下表,给出了示例数据的出解时间:
这里写图片描述

[时空复杂度]
空间上:有用状态数不是很多,105种,而且目标形状的体积也不是很大,所以空间上是完全承受的了的。

时间上:由于有很多的状态是不能被放置的,所以有了上面的分支定界和剪枝,应该可以在规定时间内出解。

[总结]
做这类题目时应该仔细分析清楚题目,抓住主要矛盾,对这道题来讲就是要仔细分析生成所有状态的方法。

[程序]
预处理程序:

program Preparation;
const
  inputfile = 'types.in';
  outputfile = 'kind.out';
  Dir: array[1..6, 1..3] of Shortint = {使坐标轴之间产生互换的数组}
((1,3,2),(3,2,1),(2,1,3),(1,2,3),(2,3,1),(3,1,2));
  Sign: array[1..8, 1..3] of Shortint = {使坐标轴产生方向变化的数组}
((-1, 1, 1),( 1,-1, 1),( 1, 1,-1),(-1,-1,-1),
 ( 1, 1, 1),(-1,-1, 1),(-1, 1,-1),( 1,-1,-1));
type
  TPoint = array[1..3] of Shortint; {一个单位立方体}
  TCube = record {一个积木}
    Count: Byte; {体积}
    Struct: array[1..4] of TPoint; {结构}
  end;
var
  Cube, Temp: TCube;
  Cubes: array[1..300] of TCube; {保存所有的积木}
  Source: array[1..300] of Byte; {这种积木是有第几总积木旋转而来}
  CubeCount, I, J, K, P, Q, N, V, mX, mY, mZ: Integer;
  head, tail: integer;

function App: Boolean; {判断一种积木是否是重复的}
var
  I, J, P, dX, dY, dZ: Integer;
  E: TPoint;
  Same: Boolean;
begin
  App := True;
  with Temp do
  begin
    for i := 1 to Count - 1 do {排序}
    begin
      P := i;
      for j := i to Count do
        if (Struct[j, 1] < Struct[P, 1]) or
           (Struct[j, 1] = Struct[P, 1]) and (Struct[j, 2] < Struct[P, 2]) or
           (Struct[j, 1] = Struct[P, 1]) and (Struct[j, 2] = Struct[P, 2]) and
           (Struct[j, 3] < Struct[P, 3]) then P := j;
      E := Struct[i];
      Struct[i] := Struct[P];
      Struct[P] := E;
    end;
    for i := 1 to CubeCount do {判断重复情况}
      if Cubes[i].Count = Count then
      begin
        dX := Cubes[i].Struct[1, 1] - Struct[1, 1];
        dY := Cubes[i].Struct[1, 2] - Struct[1, 2];
        dZ := Cubes[i].Struct[1, 3] - Struct[1, 3];
        Same := True;
        for j := 1 to Count do
          if (Cubes[i].Struct[j, 1] - Struct[j, 1] <> dX) or
             (Cubes[i].Struct[j, 2] - Struct[j, 2] <> dY) or
             (Cubes[i
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值