【问题标题】:Permutations with extra restrictions具有额外限制的排列
【发布时间】:2010-05-07 16:15:59
【问题描述】:

我有一组项目,例如:{1,1,1,2,2,3,3,3},还有一组限制性的集合,例如 {{3},{1,2} ,{1,2,3},{1,2,3},{1,2,3},{1,2,3},{2,3},{2,3}。我正在寻找项目的排列,但第一个元素必须是 3,第二个必须是 1 或 2,等等。

这样一种适合的排列是: {3,1,1,1,2,2,3}

是否有一种算法可以计算这个问题的所有排列?这类问题有名称吗?

为了说明,我知道如何为某些类型的“限制集”解决这个问题。 项目集:{1,1,2,2,3},限制条件 {{1,2},{1,2,3},{1,2,3},{1,2},{1,2 }}。这等于 2!/(2-1)!/1! * 4!/2!/2!。首先有效地排列 3,因为它是最严格的,然后在有空间的地方排列剩余的项目。

还有……多项式时间。这可能吗?

更新:这将在下面的链接中进一步讨论。上面的问题称为“计算完美匹配”,上面的每个排列限制都由插槽矩阵上的 {0,1} 表示。

【问题讨论】:

  • 您是在寻找一个计数,还是对一种也可以打印排列的算法感兴趣?无论如何,这必须有多高效?
  • 你的问题对我来说看起来很有趣,但我并不完全理解。
  • 计数就可以了。因为如果需要,我可以递归地应用计数算法来遍历或随机访问第 n 个排列。算法/分析方法必须在多项式时间内,而不是明显的“遍历所有排列并删除不符合规则的排列”算法。好问题。对我来说,方程和算法一样好。参考类似的分析方法或学术出版物也会对我有所帮助。
  • mathoverflow.net你可能会有更好的运气
  • 我对一些事情感到困惑:集合通常是无序的,并且每个元素只有一个,但我想你在谈论“有序多集合”之类的?此外,如果有 k 个限制集,这是否意味着所有排列的长度都必须为 k?输出的长度、输入多重集的大小和限制集的数量之间有什么关系?

标签: algorithm math permutation combinatorics


【解决方案1】:

这里的所有其他解决方案都是指数级的——即使在不需要的情况下也是如此。这个问题有类似的子结构,所以应该用动态规划来解决。

你想做的是写一个类来记忆子问题的解决方案:

class Counter {
  struct Problem {
     unordered_multiset<int> s;
     vector<unordered_set<int>> v;
  };

  int Count(Problem const& p) {
    if (m.v.size() == 0)
      return 1;
    if (m.find(p) != m.end())
      return m[p];
    // otherwise, attack the problem choosing either choosing an index 'i' (notes below)
    // or a number 'n'.  This code only illustrates choosing an index 'i'.
    Problem smaller_p = p;
    smaller_p.v.erase(v.begin() + i);
    int retval = 0;
    for (auto it = p.s.begin(); it != p.s.end(); ++it) {
      if (smaller_p.s.find(*it) == smaller_p.s.end())
        continue;
      smaller_p.s.erase(*it);
      retval += Count(smaller_p);
      smaller_p.s.insert(*it);      
    }
    m[p] = retval;
    return retval;
  }

  unordered_map<Problem, int> m;
};

代码说明了选择索引i,应该选择在有v[i].size()小的地方。另一种选择是选择一个数字 n,它应该是一个可以放置的位置 v 很少的数字。我会说两个决定因素中的最小值应该获胜。

此外,您还必须为 Problem 定义一个哈希函数——使用 boost 的哈希值应该不会太难。

可以通过用 set 替换向量并为 unordered_set 定义

这个解决方案可以通过制作相同的问题实例来进一步改进,除了数字被重新排列为相同的值并比较相同。

【讨论】:

  • 这很好,我看到一个好的哈希函数将如何极大地影响这个问题的复杂性。谢谢!
  • 你确定这展示了最优的子结构吗?您似乎在选择 i 和 n 看起来不错的那个,但我看不出这如何反映问题的某些结构。
  • 它展示了最优子结构,因为知道子问题的答案有助于您构建更大问题的答案——在上面的代码中,递归 Count 函数首先检查答案是否存储在地图中。
  • 我仍在努力解决这个问题。我最困难的问题是规范化子问题。我的矩阵是带有模式的二进制 100x100,实际上我正在尝试找到永久的。
  • @FullDecent:我不明白你的意思……
【解决方案2】:

您可能会考虑使用数字池的递归解决方案(在您提供的示例中,它将被初始化为 {1,1,1,2,2,3,3,3}),并决定在作为参数给出的索引,该索引放置哪个数字(当然,使用您提供的限制)。

如果你愿意,我可以提供伪代码。

【讨论】:

  • 这是正确的想法,但您想记住子问题的解决方案,否则解决方案在许多情况下是指数级的。
【解决方案3】:

你可以建一棵树。 级别 0:创建根节点。 级别 1:将第一个“限制集”中的每个项目附加为根的子项。 级别 2:将第二个限制集中的每个项目附加为每个级别 1 节点的子节点。 级别 3:将第三个限制集中的每个项目附加为每个级别 2 节点的子节点。 ...

排列计数就是最终树的叶节点数。

编辑

目前尚不清楚“项目集”{1,1,1,2,2,3,3,3} 的含义。如果这是为了限制每个值可以使用多少次(“1”可以使用 3 次,“2”可以使用两次,等等),那么我们需要多一步:

  • 在将节点附加到树之前,从项目集中删除当前路径上使用的值。如果您要附加的值仍然可用(例如,您要附加一个“1”,而“1”到目前为止只使用了两次),则将其附加到树中。

【讨论】:

  • 这行得通,但是当您早期做出的选择可能被删减时,它就不是最佳的了,例如,{1,2,...}, {{1,2,3},。 ....,{1}}:单个的 1 应该放在最后一个 slot 中,不要早期使用,只是为了发现我们以后需要它。
  • @Neil - 此解决方案假定您可以根据需要多次从集合中选择一个值。从是否应该允许的问题中不清楚。如果是,则不需要修剪 - 所有路径都有效。
  • 如果允许您从集合中选择任意次数的数字,那么为什么某些数字会在集合中重复——为什么它是多重集合?
【解决方案4】:

为了节省空间,您可以构建有向图而不是树。

  • 创建根节点。
  • 为每个项目创建一个节点 第一组,并从根链接到 新节点。
  • 为每个项目创建一个节点 第二组,并从每个第一组链接 将项目设置为每个第二个设置项目。
  • ...

排列的数量就是从根节点到最终集合的节点的路径数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2021-11-22
    • 2018-02-11
    • 2016-05-26
    • 1970-01-01
    • 2019-01-21
    相关资源
    最近更新 更多