我的问题可以简化为以下几点:
我有11
整数(球)(编号从1到11)和4
方框(箱子)(方框A, B, C, D
)。我想将11
整数的分布数量计入满足以下约束的4
框中:
A
的容量为1(一次只能容纳1个整数)B, C, D
不能包含总计最多13个我所拥有的是11
个整数及其所喜欢的框的列表(表示为x->A,B,C,D
,可以读作"整数x喜欢框A,B,C,D& #34):
1->(A, D)
,2->(B)
,3->(B)
,4->(B)
,5->(B)
,6->(C)
,7->(C,D)
,8->(C,D)
,9->(C,D)
,10->(C,D)
,11->(D)
。
在这种情况下,解决方案是8,有8种方法可以在框之间分配整数:
A = [1],B = [2,3,4,5],C = [6,8,9,10],D = [7,11]和另外7个分布,其中8,9,10在方框C和D之间分配不同。
现在在纸上做这件事很好,但我正在努力寻找一种系统的方式(蛮力)来完成满足约束的所有可能的分布。我的方法包括11个for循环(每个数字一个),每个循环遍历一个列表(每个数字都有一个填充了它喜欢的框的列表)。我在计算分配数量的同时试图强制执行约束时迷失了,是否有一种更优雅,更通用的方法将这些球解决到容器中,同时满足一些约束问题?
答案 0 :(得分:2)
(1)定义Box类。
copy.deepcopy()
(2)定义实际框。
abstract class Box {
Set<Integer> balls = new HashSet<>();
abstract boolean canAdd(int n);
boolean add(int n) {
if (!canAdd(n)) return false;
balls.add(n);
return true;
}
void remove(int n) { balls.remove(n); }
@Override public String toString() { return balls.toString(); }
}
class Box1 extends Box {
// has a capacity of 1 (only 1 integer fits into it at one time)
boolean canAdd(int n) {
return balls.size() < 1;
}
}
class Box13 extends Box {
// cannot contain 2 integers which sum up to 13
boolean canAdd(int n) {
int remain = 13 - n;
return !balls.contains(remain);
}
}
(3)定义整数的首选项。
Box A = new Box1();
Box B = new Box13();
Box C = new Box13();
Box D = new Box13();
(5)找到解决方案。
Box[][] likes = {
/* 0 */ {},
/* 1 */ {A, D},
/* 2 */ {B},
/* 3 */ {B},
/* 4 */ {B},
/* 5 */ {B},
/* 6 */ {C},
/* 7 */ {C, D},
/* 8 */ {C, D},
/* 9 */ {C, D},
/* 10 */ {C, D},
/* 11 */ {D},
};
RESULT
for (Box b1 : likes[1]) {
if (!b1.add(1)) continue;
for (Box b2 : likes[2]) {
if (!b2.add(2)) continue;
for (Box b3 : likes[3]) {
if (!b3.add(3)) continue;
for (Box b4 : likes[4]) {
if (!b4.add(4)) continue;
for (Box b5 : likes[5]) {
if (!b5.add(5)) continue;
for (Box b6 : likes[6]) {
if (!b6.add(6)) continue;
for (Box b7 : likes[7]) {
if (!b7.add(7)) continue;
for (Box b8 : likes[8]) {
if (!b8.add(8)) continue;
for (Box b9 : likes[9]) {
if (!b9.add(9)) continue;
for (Box b10 : likes[10]) {
if (!b10.add(10)) continue;
for (Box b11 : likes[11]) {
if (!b11.add(11)) continue;
System.out.printf("A=%s B=%s C=%s D=%s%n",
A, B, C, D);
b11.remove(11);
}
b10.remove(10);
}
b9.remove(9);
}
b8.remove(8);
}
b7.remove(7);
}
b6.remove(6);
}
b5.remove(5);
}
b4.remove(4);
}
b3.remove(3);
}
b2.remove(2);
}
b1.remove(1);
}
递归求解器是
A=[1] B=[2, 3, 4, 5] C=[6, 8, 9, 10] D=[7, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8, 9] D=[7, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8, 10] D=[7, 9, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 8] D=[7, 9, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 9, 10] D=[7, 8, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 9] D=[7, 8, 10, 11]
A=[1] B=[2, 3, 4, 5] C=[6, 10] D=[7, 8, 9, 11]
A=[1] B=[2, 3, 4, 5] C=[6] D=[7, 8, 9, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 9, 10] D=[1, 7, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 9] D=[1, 7, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8, 10] D=[1, 7, 9, 11]
A=[] B=[2, 3, 4, 5] C=[6, 8] D=[1, 7, 9, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 9, 10] D=[1, 7, 8, 11]
A=[] B=[2, 3, 4, 5] C=[6, 9] D=[1, 7, 8, 10, 11]
A=[] B=[2, 3, 4, 5] C=[6, 10] D=[1, 7, 8, 9, 11]
A=[] B=[2, 3, 4, 5] C=[6] D=[1, 7, 8, 9, 10, 11]
和
static void solve(Box A, Box B, Box C, Box D, Box[][] likes, int n) {
if (n > 11) {
System.out.printf("A=%s B=%s C=%s D=%s%n", A, B, C, D);
return;
}
for (Box box : likes[n]) {
if (!box.add(n)) continue;
solve(A, B, C, D, likes, n + 1);
box.remove(n);
}
}
答案 1 :(得分:1)
你的方法听起来不错。
选择一种方法来表示将球分配到方框中(例如,数组,以球为索引,方框为值)。
编写一个函数isDistributionValid(distribution)
,它接受一个分布,如果满足所有约束,则返回true
。您可能想要编写以下辅助函数:
getBallsInBox(distribution, box)
:返回一个列表,其中包含分配给box
的所有球count(distribution, box)
:返回分配中box
分配的球数anyPairSumsToTarget(list, target)
:如果true
中的任何一对值总和为list
,则返回target
(提示:使用两个嵌套的for循环)在11个循环的最里面,使用当前分布的表示来调用isDistributionValid
。如果结果为true,则递增计数器。
使用递归有一种更优雅的方式。它具有较少的重复代码,并且不必修复球的数量。想法是写一个函数
count_distributions(current_ball, last_ball, distribution)
调用该函数时,current_ball
之前的所有球应该已经分配到框中,并且分配应该存储在distribution
中。该函数返回通过将剩余的球分配给框可以生成的有效分布数。为此,该函数迭代current_ball
的可能框,并且对于每种可能性,它递归调用count_distributions(current_ball+1, last_ball, new_distribution)
并对结果求和。作为基本情况,如果current_ball
大于last_ball
,则分发完成,函数应检查分布是否有效,如果是,则返回1,否则返回0。该功能使用count_distributions(1, 11, empty_distribution)
启动。
伪代码:
function count_distributions(current_ball, last_ball, distribution):
if current_ball > last_ball:
// all the balls have been distributed
if distribution is valid:
return 1
else
return 0
sum = 0
// try each possibility for current_ball
for chosen_box in boxes that current_ball likes:
new_distribution = copy(distribution)
update new_distribution so current_ball maps to chosen_box
// recursively try remaining balls
sum += count_distributions(current_ball + 1, last_ball, new_distribution)
return sum
print(count_distributions(1, 11, empty distribution))
如果当前不完整的分发无效,则可以通过提前终止来使两个版本明显更快。每次将(球,盒)分配添加到分配时,请检查不完整分布是否有效。如果不是,则继续进行分发是没有意义的。
通过向分布表示添加更多信息,可以更有效地检查约束,但我不会详细介绍。