【问题标题】:Counting number of ways to select unique elements from list of lists计算从列表列表中选择唯一元素的方法数
【发布时间】:2013-10-20 01:46:31
【问题描述】:

SPOJ Problem 423: Assignments 有问题。

这个问题要求我计算 n 个独特主题的可能分配给 n 个独特的学生的数量,以便每个学生都得到一个他/她喜欢的主题。我想出了一种将输入解析为名为@9​​87654323@ 的列表列表的方法。每个内部列表都是一个学生喜欢的主题列表(用 0 到 n-1 之间的整数表示)。

例如对于输入:

1 1 1
1 1 1
1 1 1

我收到[[0, 1, 2], [0, 1, 2], [0, 1, 2]]

对于输入:

1 0 0 1 0 0 0 0 0 1 1 
1 1 1 1 1 0 1 0 1 0 0 
1 0 0 1 0 0 1 1 0 1 0 
1 0 1 1 1 0 1 1 0 1 1 
0 1 1 1 0 1 0 0 1 1 1 
1 1 1 0 0 1 0 0 0 0 0 
0 0 0 0 1 0 1 0 0 0 1 
1 0 1 1 0 0 0 0 0 0 1 
0 0 1 0 1 1 0 0 0 1 1 
1 1 1 0 0 0 1 0 1 0 1 
1 0 0 0 1 1 1 1 0 0 0

我得到列表[[0, 3, 9, 10], [0, 1, 2, 3, 4, 6, 8], [0, 3, 6, 7, 9], [0, 2, 3, 4, 6, 7, 9, 10], [1, 2, 3, 5, 8, 9, 10], [0, 1, 2, 5], [4, 6, 10], [0, 2, 3, 10], [2, 4, 5, 9, 10], [0, 1, 2, 6, 8, 10], [0, 4, 5, 6, 7]]

现在我需要做的是计算我可以从每个内部列表中选择唯一数字的方式的数量,以便没有元素被选择两次,并且在 0 和 n-1 之间的每个数字都在所有选择中仅选择一次。对于第一个示例输入,这是微不足道的,它是 3! = 6。但是在第二个例子中,我很难找到一种方法来计算有效选择的数量。对于第二个输入,他们给出的答案是 7588,但我不知道如何获得该答案。

我已经尝试过查找 [0,...,n-1] 的所有排列并尝试查看它们是否是基于集合成员资格的有效组合的蛮力方法,但它太慢了,它真的让我崩溃了当我尝试遍历 11! = 39916800 排列时,计算机。所以我需要做的是找到一种更有效的计算方法。

到目前为止,这是我的代码。它目前所做的只是将来自标准输入的输入解析为一个名为首选项的列表列表并将其打印到标准输出。

def main():
    t = int(raw_input())
    from itertools import repeat
    for _ in repeat(None, t):
        n = int(raw_input())
        preferences = [[] for _ in repeat(None, n)]
        for student in xrange(n):
            row = map(int, raw_input().split())
            for i in xrange(len(row)):
                if row[i] == 1:
                    preferences[student].append(i)
        print preferences


if __name__ == '__main__':
    main()

我有什么方法可以有效地计算这个吗?欢迎任何提示/提示。我不指望任何人为我解决问题。我只是对如何处理这个问题感到困惑。

【问题讨论】:

  • 你可以看看Counter
  • @mshsayem 我知道 collections.Counter。这如何适用于这个问题?
  • 我认为您应该添加 2 个重要细节。 N
  • @AbhishekBansal 是的,确实如此,但我仍然认为蛮力方法太慢了。即使对于 N
  • @ShashankGupta 更新了我的答案。希望对您有所帮助。

标签: python algorithm permutation combinatorics


【解决方案1】:

我知道我在 2005 年解决了那个问题 - 它仍然是那里的第 10 快,但使用的内存比其他公认的解决方案要少得多。今天看代码,我不知道它在做什么 - LOL ;-)

如果我没记错的话,这是“禁止位置排列”的一个例子。这是您可以使用的搜索词,以及“rook polynomials”。它归结为计算 0-1 矩阵的“永久”(另一个搜索项),这是一项计算量很大的任务。这就是为什么您在 SPOJ 上没有看到针对此问题的公认 Python 解决方案(我用 C 编写了我的)。

所以,那里没有答案,但有很多东西需要研究 ;-) 获得一个被接受的程序更多的是学习数学而不是聪明的编程。

一个提示:在我的笔记中,我看到我通过包含全 1 的特殊输入矩阵保存了 很多 个运行时间。在这种情况下,结果只是 N 的阶乘(对于 N×N 输入)。祝你好运:-)

剧透警告!

这显示了一种相对简单的方法来实现 0-1 矩阵的 Ryser 公式。行被视为普通整数,索引子集也被视为普通整数。可以添加许多微优化(例如,如果 prod 变为 0,则尽早跳出循环;如果矩阵完全为 1,则返回 math.factorial(n);等等)。

_pc = []
for i in range(256):
    c = 0
    while i:
        # clear last set bit
        i &= i-1
        c += 1
    _pc.append(c)

def popcount(i):
    "Return number of bits set."
    result = 0
    while i:
        result += _pc[i & 0xff]
        i >>= 8
    return result

def perm(a):
    "Return permanent of 0-1 matrix.  Each row is an int."
    result = 0
    n = len(a)
    for s in range(1 << n):
        prod = 1
        for row in a:
            prod *= popcount(row & s)
        if popcount(s) & 1:
            result -= prod
        else:
            result += prod
    if n & 1:
        result = -result
    return result

def matrix2ints(a):
    return [int("".join(map(str, row)), 2)
            for row in a]

def matrix_perm(a):
    "Return permanent of 0-1 matrix."
    return perm(matrix2ints(a))

【讨论】:

  • 感谢您的搜索词。我现在就开始查找它们。谢天谢地,我确实懂一点 C。希望我能顺利完成这项工作。如果这不能在 Python 中完成,那么他们为什么要允许 Python 呢?他们只是想欺骗我们吗?
  • SPOJ 通常允许您使用您喜欢的任何语言(以及他们支持的语言)。选择一种足够快的语言来解决问题是乐趣的一部分;-)
  • 我想我正在慢慢开始理解Ryser formula in this article 的第二个版本。转换为代码似乎相当容易,但我只想检查我是否有正确的想法。基本上,您首先计算集合 (0,...,n-1) 的幂集。然后为了计算最外层和的每一项,循环遍历幂集 PS,并将每个元素分配给 S。取 -1^(length(S)) 并将其乘以 i 从 0 到 n-1 的累积乘积。 ...(续)
  • 要计算乘积的每一项,循环 j 遍历 S 的所有元素,并将矩阵的元素 a_ij 添加到一个总和中,这将是外部乘积项的一项。将所有项相乘以完成乘积,最后将乘积乘以 -1^length(S),如上所述。这甚至接近正确吗?它还说,如果您按格雷码顺序处理集合,您可以将复杂度降低到 O(2^(n) * n)。但我不确定“按格雷码顺序处理集合”是什么意思。你在 2005 年的代码中这样做了吗?如果是这样,你能帮我解释一下吗? :) 谢谢!
  • 再想一想,我想我得到了格雷码订单的东西。 o.o 基本上,这意味着在外部求和中处理的每个连续集合应该只因包含或排除一个元素而有所不同……至少我认为。 :) 但是我对基本公式的想法正确吗?
【解决方案2】:

这是一个简单的实现(使用第二个示例矩阵):

>>> M = [[1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0], [1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1], [0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1], [1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1], [1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1], [0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1], [1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0]]
>>> L = len(M)
>>> M = [[k for k in xrange(L) if l[k] == 1] for l in M] # Gets the indices of '1's

>>> def count_assignment(taken,row):
       if row >= L: return 1
       c = 0
       for e in M[row]:
          if e not in taken: c = c + count_assignment(taken + [e], row + 1)
       return c

>>> count_assignment([], 0)
7588

【讨论】:

  • 这出奇的优雅和高效。我正在考虑使用递归,但我不确定如何。现在我觉得很尴尬,因为你碰巧这么容易做到。
  • 顺便说一句,如果您使用集合而不是列表,这将更有效率。更快的会员测试。
  • 它很优雅,但效率不高 - 例如,对于一个 1 的 N×N 矩阵,它会花费与 N! 成比例的时间。不过,它非常适合稀疏矩阵。
  • @TimPeters 是的,我没有得到接受我的答案(即使在添加了带有 sets 和 set.union 方法的修改之后)。我目前正在研究车多项式,因为使用组合似乎是在时间限制下进行的唯一方法。这仍然是一种看待递归问题的好方法,这就是我给出这个答案 +1 的原因。
  • @TimPeters Ugh 线性代数...不好玩!
【解决方案3】:

问题是计算图中最大二分匹配的总数。

以下来自Wikipedia article 的摘录可能会有所帮助

图中的匹配数称为细谷指数 图形。计算这个数量是#P-complete。它仍然存在

P-complete 在计算给定二分图中完美匹配数的特殊情况下,因为计算永久

任意0-1矩阵(另一个#P-complete问题)是相同的 作为计算二分图中完美匹配的数量 将给定矩阵作为其邻接矩阵。然而,有 存在一个完全多项式时间随机逼近方案 计算二分匹配的数量。[10]一个非凡的定理 Kasteleyn 指出,平面中完美匹配的数量 图形可以通过 FKT 在多项式时间内精确计算 算法。

完全图Kn中完美匹配的数量(n为偶数) 由双阶乘 (n − 1) 给出!!.[11]的数量 完全图中的匹配,不限制匹配 完美,由电话号码给出。[12]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-27
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 2017-04-12
    • 2021-11-11
    相关资源
    最近更新 更多