算法:递归求解石板切割问题

一、实验要求

给出一长度、高度均已知的木板,以及一系列长度、高度已知的目标板。

要求:

① 目标板长宽要对齐木板长宽,不能旋转。

② 必须沿长边或短边一刀切,将木板分割为两个矩形。

③ 目标板的一角应与木板一角重合。

④ 目标板大小均不同,长度都为整数。

请给出使得木板利用率最高的切割方案。

二、问题分析与方案设计

切割部分

想要从木板中切割出一个图形,至少需要两刀,而这两刀有着不同的切法,且不同的切法会影响到最终结果。

outside_default.png

如上图,对图中绿色块进行切分,可以第一刀沿竖直方向切割,第二刀沿着水平方向切割(way1),或者相反,第一刀沿水平方向 切割,第二刀沿着竖直方向切割。所以,我们求面积最大值时应该在两种切割方向求最大值。

max(sharp(x, y - aims[i].second, x - aims[i].first, aims[i].second, tmp, ways, used + aims[i].first * aims[i].second), sharp(x - aims[i].first, y, aims[i].first, y - aims[i].second, tmp, ways, used + aims[i].first * aims[i].second));

以上为实际实验方案中的代码表示。

划分

要考虑以上两种方案,划分则显得稍微复杂,并非简单的递归那么简单,这是因为划分为两块后,两块的目标块是共用的,不能等到第一块切割完后又按照上一层的目标块去处理另一块,如果这样做,则会导致结果偏大。

outside_default.png

由于目标块互斥的属性,我做了如下考虑:

依照二进制枚举的思想,对所有目标块进行划分,划分为两个组,然后对所有组,再去求它们的最大值。

具体想法:

①由数学推导,n个元素具有2^n^种分组方法,于是可以令一个数i为2^n^-1;

②引入一个变量sym = 1;

③使得i与sym作按位与运算,为1,进入t1板,为2,进入t2板;

④迭代,sym左移一位,完成数字i的分组;

⑤上一组计算完成后,数字 i--;

当数字i减至-1时,完成所有枚举。

以下为实现:

int sharp(int x1, int y1, int x2, int y2, vector<pair<int, int>> aims, vector<pair<int, int>> ways, int _used)
{
    //该重载sharp函数的作用是,利用二进制进行分流枚举
    int usemax = 0, sym = 1, used;   //usedmax 用于记录最大值,sym用于二进制枚举移动,used获取递归返回值
    vector<pair<int, int>> t1, t2;   //t1,t2 作为两个新动态数组存放枚举后目标
    int i = pow(2, aims.size()) - 1; //当有i个元素时,由二进制枚举,共有2^i种划分
    for (; i != -1; i--)
    {
        sym = 1;
        t1.clear();
        t2.clear();
        for (int j = 0; j < aims.size(); j++)
        {
            i &sym ? t1.push_back(aims[j]) : t2.push_back(aims[j]);
            sym <<= 1;
        } //二进制枚举,公众号推文前有发过
        used = sharp(x1, y1, t1, ways, _used) + sharp(x2, y2, t2, ways, _used);
        //进入分流后
        if (used > usemax)
            usemax = used;
        //记录并返回面积最大值
    }
    return usemax;
}

跟踪

由题目要求,我们知道,题目还需要我们给出具体的切割方案,所以,在递归过程中,还要进行跟踪。

这一块我思考了很久才解决,具体的解决方案是:

①开一个数组,数组长度小于木板面积,数组类型为vector;

②设置跟踪的递归出口为:当再也无法继续切割时,以当前的面积s作为数组的角标,记录下具体切割的顺序。

最后,通过返回的最大面积,遍历 数组[maxs] ;读出切割顺序。

vector<pair<int, int>> way[1000]; //用于记录对应S取值的路径

切割函数

在做了以上优化之后,切割函数只需要做一件事:给出木板的x,y及目标块序列,返回最大面积,并设置跟踪的递归出口。

如何判断一个目标块能不能切以及跟踪出口如何设立?

可以通过判断目标块的长短边是否 小于木块的长短边,并设置flag,倘若flag始终没有变化,说明所有目标块均不能切,为跟踪的递归出口条件。

int sharp(int x, int y, vector<pair<int, int>> aims, vector<pair<int, int>> ways, int used)
  {
      //有x,y,aims,求最大使用面积并返回
      vector<pair<int, int>> tmp = aims; //tmp数组作为临时数组用于剔除已经切割的目标
      int use, usemax = 0, flag = 1;//路径记录的标志:倘若全部无法切割,说明到达一条路径末
      for (int i = 0; i < aims.size(); i++)
      {
          if (aims[i].first > x || aims[i].second > y)
              continue;
          else
          {
              flag = 0;
              tmp.erase(tmp.begin() + i);
              ways.push_back(aims[i]);
              use = aims[i].first * aims[i].second + max(sharp(x, y - aims[i].second, x - aims[i].first, aims[i].second, tmp, ways, used + aims[i].first * aims[i].second),
                                                         sharp(x - aims[i].first, y, aims[i].first, y - aims[i].second, tmp, ways, used + aims[i].first * aims[i].second));
              if (use >= usemax)
                  usemax = use;
          }
          ways.pop_back();//重新开始循环前,需要把剔除的拿回,重置条件
          tmp = aims;
      }
      if (flag)
          way[used] = ways;
      return usemax;
  }

三、代码实现

完成以上分析后,就得到了代码实现方案

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

vector<pair<int, int>> way[1000]; //用于记录对应S取值的路径
int sharp(int x, int y, vector<pair<int, int>> aims, vector<pair<int, int>> ways, int used);
int sharp(int x1, int y1, int x2, int y2, vector<pair<int, int>> aims, vector<pair<int, int>> ways, int _used)
{
    //该重载sharp函数的作用是,利用二进制进行分流枚举
    int usemax = 0, sym = 1, used;   //usedmax 用于记录最大值,sym用于二进制枚举移动,used获取递归返回值
    vector<pair<int, int>> t1, t2;   //t1,t2 作为两个新动态数组存放枚举后目标
    int i = pow(2, aims.size()) - 1; //当有i个元素时,由二进制枚举,共有2^i种划分
    for (; i != -1; i--)
    {
        sym = 1;
        t1.clear();
        t2.clear();
        for (int j = 0; j < aims.size(); j++)
        {
            i &sym ? t1.push_back(aims[j]) : t2.push_back(aims[j]);
            sym <<= 1;
        } //二进制枚举,公众号推文前有发过
        used = sharp(x1, y1, t1, ways, _used) + sharp(x2, y2, t2, ways, _used);
        //进入分流后
        if (used > usemax)
            usemax = used;
        //记录并返回面积最大值
    }
    return usemax;
}

int sharp(int x, int y, vector<pair<int, int>> aims, vector<pair<int, int>> ways, int used)
{
    //有x,y,aims,求最大使用面积并返回
    vector<pair<int, int>> tmp = aims; //tmp数组作为临时数组用于剔除已经切割的目标
    int use, usemax = 0, flag = 1;//路径记录的标志:倘若全部无法切割,说明到达一条路径末
    for (int i = 0; i < aims.size(); i++)
    {
        if (aims[i].first > x || aims[i].second > y)
            continue;
        else
        {
            flag = 0;
            tmp.erase(tmp.begin() + i);
            ways.push_back(aims[i]);
            use = aims[i].first * aims[i].second + max(sharp(x, y - aims[i].second, x - aims[i].first, aims[i].second, tmp, ways, used + aims[i].first * aims[i].second),
                                                       sharp(x - aims[i].first, y, aims[i].first, y - aims[i].second, tmp, ways, used + aims[i].first * aims[i].second));
            if (use >= usemax)
                usemax = use;
        }
        ways.pop_back();//重新开始循环前,需要把剔除的拿回,重置条件
        tmp = aims;
    }
    if (flag)
        way[used] = ways;
    return usemax;
}

int main()
{
    vector<pair<int, int>> aims;
    vector<pair<int, int>> ways;
    aims.push_back(make_pair(2, 3));
    aims.push_back(make_pair(4, 2));
    aims.push_back(make_pair(4, 4));
    aims.push_back(make_pair(6, 1));
    int maxs;
    maxs = sharp(8, 4, aims, ways, 0);
    cout << maxs << endl;
    for (int i = 0; i < way[maxs].size(); i++)
        cout << way[maxs][i].first << ',' << way[maxs][i].second << endl;
}

四、实验演示

实验示例数据使用老师ppt所给的测试数据。

outside_default.png

int main()
{
    vector<pair<int, int>> aims;
    vector<pair<int, int>> ways;
    aims.push_back(make_pair(2, 3));
    aims.push_back(make_pair(4, 2));
    aims.push_back(make_pair(4, 4));
    aims.push_back(make_pair(6, 1));
    int maxs;
    maxs = sharp(8, 4, aims, ways, 0);
    cout << maxs << endl;
    for (int i = 0; i < way[maxs].size(); i++)
        cout << way[maxs][i].first << ',' << way[maxs][i].second << endl;
}

实验输出:

24
4,4
4,2

经验算,24/32 = 75%与答案一致

并给出切割方案为,先切割(4,4)的目标块,再切割(4,2)的目标块。

五、尚可优化的地方

•可以通过比较max中元素的大小,确定切割是横切还是竖切,由于添加过程并不算困难,暂且先略过。•倘若目标块不沿角,该算法还无法解决,普适性较差,但能满足题目要求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值