0-1不定方程的求解

最近遇到这样的一个问题:如何用p个大小不一的1×ki,i=1...p的方块铺满一个M×N方格,方块不能旋转或横放。

也就是说,给出如下方程组的一个有效解。

以上方程组的第一行表示,方格的每一列都必须被填满;第二行表示,每个方块只能被使用一次;第三行表示,所有方块都必须被使用。

对于求解这个问题,一个最简单的方法就是穷举,但是很可惜,穷举的空间为2p,这意味着当p较大时,需要费很大的劲才能求解出此问题。因此,我的思路是,寻找一种方法,对这个空间进行缩小,在缩小的空间内进行搜索。以下是对我的思路的一种综述。

观察以上方程组,我们可以发现,如果这些方块能铺满在这个方格的话,那意味着,列于列之间是可以交换的,即列于列之间的顺序无关。因此,我们完全可以在这些方块中随便选择一个出来,然后填入方格的第一列,然后根据这一列剩余的空间,再在剩余的方块中选择可能的方块填入,当填完第一列后,重复以上步骤即可。因此,我们的问题可以先缩小为填充一列,即寻找k1x1+...+kpxp=M的有效解。

在给出求解方案前,先给出两个事实:

  1)由于xi只能取0或1,因此若在方程中存在ki>M,那么其相应的xi必定为0,否则左边的和必定大于M。

  2)对于方程k1x1+...+kpxp=M而言,若存在一组解,第i个值为1,则方程k1x1+...+k(i-1)x(i-1)+k(i+1)x(i+1)+...+kpxp=M-ki的任意一个解与xi=1构成原方程的解。

根据以上两个事实,可以构建如下算法:

  0. 设置有效集为{k1, k2, ..., kp};

  1. 从有效集中选择一个ki,将其相应的xi设置为1;

  2. 计算M-ki,根据此值以及1)可以过滤掉部分必定为0的xj,得到新的有效集{ki1, ki2, ...};

  3. 重复1-2步骤,直至M-ki为0,则得到一个有效解,或者M-ki<0,则这条路径不是有效解,退回重新构造解,直到所有可能性被搜索完。

根据以上算法可以知道,若解存在,则可以有效地找出解来,但是若解不存在,则依然需要遍历整个解空间,这是这个算法的最大问题,也是我还不清楚如何解决的一个地方。另外,此算法的关键地方在于2,即如何构建新的有效集。而我的思路是采取快速排序中分组!

根据以上算法,可以写出如下代码(JavaScript):

 1 function getResult(array, start, end, target) {
 2     var k = partion(array, start, end, target);
 3     return _getResult(array, start, k, target);
 4 }
 5 
 6 function _getResult(array, start, end, target) {
 7     if (target != 0 && end < start) return -2; // -2只是表明当前路径没找到有效解
 8     if (target == 0) return -1; // 由于array[start..result]是保存解的地方,而target=0意味着xi全等于0,因此返回-1
 9     for (var i = end; i >= start; --i) {
10         var remain = target - array[i]; 
11         var k = partion(array, start, i - 1, remain);
12         var result = _getResult(array, start, k, remain);
13         if (result != -2) { 
14             swap(array, ++result + start, i);
15             return result;
16         }
17     }
18     return -2;
19 }
20 
21 function partion(array, start, end, target) {
22     for (var i = start - 1, j = start; j <= end; ++j) {
23         if (array[j] <= target) {
24             swap(array, ++i, j);
25         }
26     }
27     return i;
28 }
29 
30 function swap(array, i, j) {
31     var temp = array[i];
32     array[i] = array[j];
33     array[j] = temp;
34 }

这里将数组array[start..result]作为解的存储地方,采取这种方式的原因是,1)不用另外构建数组用于存储结果;2)根据得到的结果result和array可以很方便的实现原问题中每个方块必须使用且只能使用一次的限制,且通过一次循环即可解决原方程组的问题。

测试的代码如下:

 1 var a = new Array(1000);
 2 var target = 2345;
 3 (function() {
 4     for (var i = 0; i < a.length; ++i)
 5         a[i] = i + 1;
 6     var result = getResult(a, 0, a.length - 1, target), sum = 0;
 7     for (var i = 0; i <= result; ++i) sum += a[i];
 8     console.log("target: " + target + ", result: " + sum);
 9     console.log("[" + a.slice(0, result + 1) + "], length: " + (result + 1) + ", iterations: " + n);
10 })();

结果如下:

target: 2345, result: 2345
[346,999,1000], length: 3, iterations: 4

 
其中的iterations用于检查_getResult的调用次数,这里的代码并没有反应出来。
 
总结:
  1)快速排序的变形用处颇多,例如这里以及选择k位数问题;
  2)如果无解,这里依然需要遍历整个解空间,为了解决这个问题,我试过引入一个前置判断,先计算此方程有多少个解,但时间复杂度为O(pM),如果M较大时,则需要开辟大量空间(长度为M的数组)来计算,昂贵。。。
  3)这个问题可以转化为整数规划或动态规划来做,但是感觉太复杂,不如这里来得简洁。
 
如果你不觉意间看到了这篇文章且有不同于这里的思路,希望能留下您宝贵的评论,谢谢!

 

转载于:https://www.cnblogs.com/texvnars/archive/2013/05/27/3101512.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值