我正在寻找一种方法来最大化共同集合的价值,该集合包含来自多个来源的贡献,每个来源都有固定数量的贡献。
示例问题:每人3人都有一手牌。每只手都包含一套独特的套装,但3套可能会重叠。每个玩家可以选择三张牌来贡献中间牌。如何最大化9个贡献卡的总和
答案 0 :(得分:1)
看起来像packing problem,您要在其中打包原始集的3个不相交的子集,每个子集大小为3,并最大化总和。您可以将其表述为ILP。不失一般性,我们可以假设这些卡代表从1到N的自然数。
a_i in {0,1}
表示玩家A
是否会使用值为i
的卡片,其中i
位于{1,...,N}
。请注意,如果玩家A
手中没有卡i
,则a_i
在开头设置为0
。b_i
和c_i
定义B
和C
个变量。m_i in {0,1}
表示卡片i
是否会出现在中间位置,即其中一个玩家会播放价值为i
的卡片。现在你可以说:
最大化 Sum(m_i . i)
,但需视:
对于每个i in {1,...N,}
:
a_i, b_i, c_i, m_i
位于{0, 1}
m_i = a_i + b_i + c_i
Sum(a_i) = 3
,Sum(b_i) = 3
,Sum(c_i) = 3
<强>讨论强>
注意约束1和2,强制中间每张卡的唯一性。
我不确定使用此程序的商业或非商业解决方案可以处理多大的问题,但请注意,这实际上是一个二进制线性程序,可能比一般的ILP更难解决,所以可能值得尝试你想要的尺寸。
答案 1 :(得分:1)
整数编程听起来像一种可行的方法。在没有保证的情况下,这个问题也让人觉得NP难,这意味着:没有通用算法打败暴力(没有关于可能输入的假设; IP解算器实际上假设很多/正在调整实际问题)。 / p>
(替代的现成方法:约束编程和SAT解算器; CP:易于制定,在组合搜索方面更快,但在最大化方面使用分支绑定方式不太好; SAT:很难制定,因为计数器需要建立,非常快速的组合搜索和再次:没有最大化的概念:需要像变换这样的决策问题。
这里有一些基于python的完整示例解决了这个问题(在硬约束版本中;每个玩家都必须玩他所有的牌)。因为我使用的是cvxpy,所以代码是数学风格的,尽管不知道python或lib,但应该很容易阅读!
在介绍代码之前,请注意以下几点:
一般说明:
N_PLAYERS = 40 , CARD_RANGE = (0, 400) , N_CARDS = 200 , N_PLAY = 6
改进:问题
改进:效果
代码:
import numpy as np
import cvxpy as cvx
np.random.seed(1)
""" Random problem """
N_PLAYERS = 5
CARD_RANGE = (0, 20)
N_CARDS = 10
N_PLAY = 3
card_set = np.arange(*CARD_RANGE)
p = np.empty(shape=(N_PLAYERS, N_CARDS), dtype=int)
for player in range(N_PLAYERS):
p[player] = np.random.choice(card_set, size=N_CARDS, replace=False)
print('Players and their cards')
print(p)
""" Preprocessing:
Conflict-constraints
-> if p[i, j] == p[x, y] => don't allow both
Could be made more efficient
"""
conflicts = []
for p_a in range(N_PLAYERS):
for c_a in range(N_CARDS):
for p_b in range(p_a + 1, N_PLAYERS): # sym-reduction
if p_b != p_a:
for c_b in range(N_CARDS):
if p[p_a, c_a] == p[p_b, c_b]:
conflicts.append( ((p_a, c_a), (p_b, c_b)) )
# print(conflicts) # debug
""" Solve """
# Decision-vars
x = cvx.Bool(N_PLAYERS, N_CARDS)
# Constraints
constraints = []
# -> Conflicts
for (p_a, c_a), (p_b, c_b) in conflicts:
# don't allow both -> linearized
constraints.append(x[p_a, c_a] + x[p_b, c_b] <= 1)
# -> N to play
constraints.append(cvx.sum_entries(x, axis=1) == N_PLAY)
# Objective
objective = cvx.sum_entries(cvx.mul_elemwise(p.flatten(order='F'), cvx.vec(x))) # 2d -> 1d flattening
# ouch -> C vs. Fortran storage
# print(objective) # debug
# Problem
problem = cvx.Problem(cvx.Maximize(objective), constraints)
problem.solve(verbose=False)
print('MIP solution')
print(problem.status)
print(problem.value)
print(np.round(x.T.value))
sol = x.value
nnz = np.where(abs(sol - 1) <= 0.01) # being careful with fp-math
sol_p = p[nnz]
assert sol_p.shape[0] == N_PLAYERS * N_PLAY
""" Output solution """
for player in range(N_PLAYERS):
print('player: ', player, 'with cards: ', p[player, :])
print(' plays: ', sol_p[player*N_PLAY:player*N_PLAY+N_PLAY])
输出:
Players and their cards
[[ 3 16 6 10 2 14 4 17 7 1]
[15 8 16 3 19 17 5 6 0 12]
[ 4 2 18 12 11 19 5 6 14 7]
[10 14 5 6 18 1 8 7 19 15]
[15 17 1 16 14 13 18 3 12 9]]
MIP solution
optimal
180.00000005500087
[[ 0. 0. 0. 0. 0.]
[ 0. 1. 0. 1. 0.]
[ 1. 0. 0. -0. -0.]
[ 1. -0. 1. 0. 1.]
[ 0. 1. 1. 1. 0.]
[ 0. 1. 0. -0. 1.]
[ 0. -0. 1. 0. 0.]
[ 0. 0. 0. 0. -0.]
[ 1. -0. 0. 0. 0.]
[ 0. 0. 0. 1. 1.]]
player: 0 with cards: [ 3 16 6 10 2 14 4 17 7 1]
plays: [ 6 10 7]
player: 1 with cards: [15 8 16 3 19 17 5 6 0 12]
plays: [ 8 19 17]
player: 2 with cards: [ 4 2 18 12 11 19 5 6 14 7]
plays: [12 11 5]
player: 3 with cards: [10 14 5 6 18 1 8 7 19 15]
plays: [14 18 15]
player: 4 with cards: [15 17 1 16 14 13 18 3 12 9]
plays: [16 13 9]
答案 2 :(得分:0)
对每只手进行排序,删除重复值。删除任何手牌的第10张最高牌(3手牌* 3张牌/手牌,加1张)以外的任何牌照:任何人都无法提供低牌。 出于会计目的,按卡片值创建一个目录,显示哪个手拿着每个值。例如,给予玩家A,B,C和这些牌
A [1,1,1,6,4,12,7,11,13,13,9,2,2]
B [13,2,3,1,5,5,8,9,11,10,5,5,9]
C [13,12,11,10,6,7,2,4,4,12,3,10,8]
我们会对手进行分类和重复删除。 2是手牌c的第10高牌,所以我们放弃所有2及以下的值。然后构建目录
A [13, 12, 11, 9, 7, 6]
B [13, 11, 10, 9, 8, 5, 3]
C [13, 12, 11, 10, 8, 7, 6, 4, 3]
Directory:
13 A B C
12 A C
11 A B C
10 B C
9 A B
8 B C
7 A B
6 A C
5 B
4 C
3 B C
现在,您需要实现一个回溯算法,以某种顺序选择卡片,获得该订单的总和,并与目前为止的最佳值进行比较。我建议你遍历目录,选择一只手来获取最高剩余卡,当你完全耗尽贡献者时,或者当你获得9张牌时回溯。
我建议您保留一些参数以便修改调查,尤其是当您进入较低值时。
制作一个高起跑目标:依次循环双手,拿出手中剩余的最高牌。在这种情况下,循环A-B-C,我们将
13,11,12,9,10,5,7,5,6 =&gt; 81 //注意:因为我选择的值 //这恰好提供了最佳解决方案。 //它将在很多桥头问题空间中做到这一点。
记录每只手牌所贡献的牌数;当一个人给出了3张牌时,以某种方式取消了它:检查选择代码,或者从目录的本地副本中删除它。
当您沿着选择列表走下去时,只要剩余的卡片不足以达到最佳总数,就会修剪搜索。例如,如果您在7张牌之后总共有71张牌,并且剩余的最高牌数为5张,则停止:您不能使用5 + 4获得81张。
这会让你感动吗?