【问题标题】:Lexicographic Rank of a Set Partitioned Into Groups划分为组的集合的词典等级
【发布时间】:2021-03-08 23:23:31
【问题描述】:

给定一组 8 个连续数字{0..7} 分成 4 组大小为 2,每组中的数字按升序排列,如何为该集合生成排名?排名应按字典顺序排列,算法的复杂度最好是线性的。

分区示例:

{{0 1} {2 3} {4 5} {6 7}} // Rank 0
...
{{6 7} {4 5} {2 3} {0 1}} // Rank 2519

因为每个组中的数字是按升序排列的,所以这些组被有效地视为组合,而不是排列,所以一个组包含例如{5 4} 永远不会发生。

这组数字如何在[0, 2520) (8C2 * 6C2 * 4C2) 范围内按顺序排列?

目前我将每个组的排名计算为 8C2 组合,然后将每个排名组合在一起,将其视为 base-28 数字。这显然会在排名中留下差距,这在我的情况下是不可取的。但是,就其价值而言,这是我目前的排名。

#include <array>
using std::array;
#include <cstdint>
#include <cstddef>
#include <iostream>
using std::cout;
using std::endl;

// Calculates n!.
uint32_t factorial(uint32_t n)
{
  return n <= 1 ? 1 : n * factorial(n - 1);
}

// Calculate nCk: n!/((n-k)!*k!).
uint32_t choose(uint32_t n, uint32_t k)
{
  return (n < k)
    ? 0
    : factorial(n) / (factorial(n - k) * factorial(k));
}

template<size_t N, size_t K>
class CombinationRanker
{
  array<array<uint32_t, K+1>, N+1> choices;

public:
  /**
   * Initialize a precomputed array of nCk (N and K inclusive).
   */
  CombinationRanker()
  {
    for (unsigned n = 0; n <= N; ++n)
      for (unsigned k = 0; k <= K; ++k)
        this->choices[n][k] = choose(n, k);
  }

  /**
   * Get the rank of a combination.
   * @param comb A combination array of size K in ascending order.
   */
  uint32_t rank(const array<uint8_t, K> comb) const
  {
    // Formula: (nCk) - ((n-c_1)Ck) - ((n-c_2)C(k-1)) - ... - ((n-c_k)C1)
    // That assumes 1-based combinations with ranks starting at 1, so each
    // element in the combination has 1 added to it, and the end result has 1
    // subtracted from it to make the rank 0-based.
    uint32_t rank = this->choices[N][K];

    for (unsigned i = 0; i < K; ++i)
      rank -= this->choices[N - (comb[i] + 1)][K - i];

    return rank - 1;
  }
};

int main(int argc, char* argv[])
{
  CombinationRanker<8, 2> ranker;

  array<array<uint8_t, 2>, 4> nums =
  {{
    {0, 1}, {2, 3}, {4, 5}, {6, 7}
  }};

  // Horribly sparse rank.
  unsigned rank =
    ranker.rank(nums[0]) * 28 * 28 * 28 +
    ranker.rank(nums[1]) * 28 * 28 +
    ranker.rank(nums[2]) * 28 +
    ranker.rank(nums[3]);

  cout << rank << endl; // 10835, but I want 0.

  return 0;
}

我已将帖子标记为 C++,因为这是我正在使用的语言;但是,另一种语言的答案很好。这更像是一个数学问题,但我正在寻找一个我作为程序员而不是数学家可以理解的答案,并且代码 sn-p 在这方面会有所帮助。

【问题讨论】:

    标签: c++ combinatorics


    【解决方案1】:

    这就是我想出的。它的复杂性是二次方的,这不是最大的,但它可以解决问题。基本算法如下。

    给定一组从 [0..7] 划分为无序对的序列号,循环遍历每一对并找到其在符合条件的对中的排名 排除它前面的数字。然后将每个等级乘以其变量 根据。每个等级的变量基数是6C2*4C2*2C24C2*2C22C2

    例如,对于{{2,3}, {6,7}, {4,5}, {0,1}}

    • {2, 3} 排名第 13。
    • {6, 7} 在不包括 2 和 3 的配对中排名第 14。
    • {4, 5} 在不包括 2、3、6 和 7 的配对中排名第 5。
    • {0, 1} 被忽略。

    总之,13*6C2*4C2*2C2 + 14*4C2*2C2 + 5*2C2 = 1259

    其他例子:

    • {{0, 1}, {2, 3}, {4, 5}, {6, 7}} -&gt; 0
    • {{2, 3}, {6, 7}, {4, 5}, {0, 1}} -&gt; 1259
    • {{2, 4}, {0, 1}, {3, 5}, {6, 7}} -&gt; 1260
    • {{6, 7}, {4, 5}, {2, 3}, {0, 1}} -&gt; 2519

    这是代码中的算法。为了简洁起见,我已经硬编码了很多。

    #include <iostream>
    using std::cout;
    using std::endl;
    #include <array>
    using std::array;
    #include <cstdint>
    
    typedef array<uint8_t, 2> pair_t;
    
    /**
     * @param set A set of 8 sequential numbers, [0..7], partitioned into unordered
     * pairs.
     */
    uint32_t rank(const array<pair_t, 4>& set) {
      // All 28 (8C2) possible unordered subsets of the set of 8 sequential
      // numbers, [0..7], in lexicographic order.  Hard-coded here for brevity.
      array<pair_t, 28> pairs = {{
        {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7},
                {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}, {1, 7},
                        {2, 3}, {2, 4}, {2, 5}, {2, 6}, {2, 7},
                                {3, 4}, {3, 5}, {3, 6}, {3, 7},
                                        {4, 5}, {4, 6}, {4, 7},
                                                {5, 6}, {5, 7},
                                                        {6, 7},
      }};
    
      // Variable base for each rank "digit" (the base corresponding to the rank of
      // each subset): 6C2*4C2*2C2, 4C2*2C2, 2C2.  Again, hard-coded for brevity.
      array<uint32_t, 3> bases = {{90, 6, 1}};
    
      // Now rank the set.
      uint32_t rank = 0;
      // Rank among this many pairs.  For N=8, 8C2->6C2->4C2->2C2 (28->15->6->1).
      unsigned numRemaining = 28; // N*(N-1)/2
      array<pair_t, 28> remaining = pairs;
    
      // Loop over the first three unordered subsets.  The last isn't needed for
      // ranking--n from [0...(N-2)/2).
      for (unsigned n = 0; n < 3; ++n)
      {
        unsigned remainingInd = 0;
        const pair_t& sPair = set[n];
    
        for (unsigned r = 0; r < numRemaining; ++r)
        {
          const pair_t& rPair = remaining[r];
    
          if (sPair == rPair)
          {
            // Found the pair: rank it relative to the ramining pairs, and multiply
            // it by the base for digit n.
            rank += r * bases[n];
          }
          else if (
            sPair[0] != rPair[0] && sPair[0] != rPair[1] &&
            sPair[1] != rPair[0] && sPair[1] != rPair[1]
          )
          {
            // The pair excludes the numbers in set[n], so keep it in the
            // list of remaining pairs for the next digit's rank.
            remaining[remainingInd++] = rPair;
          }
        }
    
        // Number of remaining pairs.
        numRemaining = remainingInd;
      }
    
      return rank;
    }
    
    int main(int argc, char* argv[])
    {
      // Examples pairs.
      array<array<pair_t, 4>, 7> sets = {{
        {{{0, 1}, {2, 3}, {4, 5}, {6, 7}}},
        {{{0, 1}, {2, 3}, {4, 6}, {5, 7}}},
        {{{0, 1}, {2, 3}, {4, 7}, {5, 6}}},
        {{{0, 1}, {2, 3}, {5, 6}, {4, 7}}},
        // snip
        {{{2, 3}, {6, 7}, {4, 5}, {0, 1}}},
        // snip
        {{{6, 7}, {4, 5}, {1, 3}, {0, 2}}},
        {{{6, 7}, {4, 5}, {2, 3}, {0, 1}}},
      }};
    
      for (unsigned i = 0; i < 7; ++i)
      {
        const array<pair_t, 4>& set = sets[i];
    
        cout << rank(set) << ": ";
    
        for (unsigned j = 0; j < 4; ++j)
          cout << '{' << (unsigned)set[j][0] << ", " << (unsigned)set[j][1] << '}';
    
        cout << endl;
      }
    
      return 0;
    }
    
    

    输出:

    0: {0, 1}{2, 3}{4, 5}{6, 7}
    1: {0, 1}{2, 3}{4, 6}{5, 7}
    2: {0, 1}{2, 3}{4, 7}{5, 6}
    3: {0, 1}{2, 3}{5, 6}{4, 7}
    1259: {2, 3}{6, 7}{4, 5}{0, 1}
    2518: {6, 7}{4, 5}{1, 3}{0, 2}
    2519: {6, 7}{4, 5}{2, 3}{0, 1}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-19
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 2012-09-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多