如何索引双重限制的整数分区?

时间:2019-06-22 18:16:32

标签: c++ integer-partition

枚举具有以下2个限制的正整数的所有分区时:

  • 每个分区的大小始终为PartitionSize
  • 这些分区的所有元素都小于或等于MaxVal,并且大于零。

...我面临着对这些分区进行编号/索引的任务,我可以存储它们的索引,以后再检索它们以从任意索引快速重新生成一个分区的元素。索引不必是连续的。

Q :计算此类分区索引的最佳方法是什么?

下面列出了生成这些分区的函数:

void GenPartitions(const unsigned int myInt, const unsigned int PartitionSize, unsigned int MaxVal)
{
    if ((MaxVal = MaxPartitionVal(myInt, PartitionSize, MaxVal)) == 0)
        return;

    unsigned int MinVal = 1;
    unsigned int idx_Last = PartitionSize - 1;
    unsigned int RightSum = MaxVal;     //Sum to the right of the Decrement Point (inclusive)
    unsigned int idx_Dec = idx_Last;    //The point that needs to be decremented
    vector<unsigned int> partition(PartitionSize);

    partition[idx_Last] = MaxVal;   //Initiallize first partition

    do {
        unsigned int cur = idx_Dec - 1;
        unsigned int LeftRemain = myInt - RightSum - (idx_Dec - 1) * MinVal; //Calculate the remainder to the left

        while (LeftRemain > partition[idx_Dec]) //While the remainder is too big to satisfy the left to right ascending ordering.
        {
            LeftRemain -= partition[idx_Dec] - 1;   //
            partition[cur--] = partition[idx_Dec];      
        }
        partition[cur] = LeftRemain;    //Last remainder

        for (unsigned int i = 0; i < cur; i++)  //Set the elements where the reminder did not reach.
            partition[i] = MinVal;

                for (auto d : partition)        //DISPLAY THE PARTITON HERE ...or do sth else with it.
                    std::cout << setw(2) << d << ",";
                std::cout << endl;

        for (idx_Dec = 0; (idx_Dec < idx_Last) && (partition[idx_Dec] + 1 > partition[idx_Dec + 1]); idx_Dec++);    //Find the rising edge
        unsigned int val_1stUp = partition[idx_Dec]+1;
        for (++idx_Dec; (idx_Dec <= idx_Last) && (val_1stUp > partition[idx_Dec] - 1); idx_Dec++);  //Find the falling edge occuring AFTER the rising edge.

        if (idx_Dec > idx_Last) 
            break;  //Could not find the falling edge. We are done.

        partition[idx_Dec]--;   //Decrement at the Decrement Point
                //std::cout << setw((idx_Dec*3)+1) << "" << "v" << endl;    //Show the Decrement Points

        RightSum = 0;       //This needs optimization. There is no need to start from the Decrement Point every time.  This sum can be adjusted on-the-go, as changes are made to the partition.
        for (unsigned int i = idx_Dec; i <= idx_Last; i++)      //Calculate the sum to the right of the Decrement Point (inclusive). This needs optimization.
            RightSum += partition[i];

    } while(true); 
}

请注意,此函数会生成分区,其中每个分区中的所有元素从最小到最大(从左到右)排序。此功能不会损坏。

分区本身之间的顺序(垂直)按字典顺序排列。我不愿意失去它,但没有它我可以活下去。

SAMPLE OUTPUT OF: GenPartitions(20, 4, 10):
1, 1, 8,10
1, 2, 7,10
1, 3, 6,10
2, 2, 6,10
1, 4, 5,10
2, 3, 5,10
2, 4, 4,10
3, 3, 4,10
1, 1, 9, 9
1, 2, 8, 9
1, 3, 7, 9
2, 2, 7, 9
1, 4, 6, 9
2, 3, 6, 9
1, 5, 5, 9
2, 4, 5, 9
3, 3, 5, 9
3, 4, 4, 9
1, 3, 8, 8
2, 2, 8, 8
1, 4, 7, 8
2, 3, 7, 8
1, 5, 6, 8
2, 4, 6, 8
3, 3, 6, 8
2, 5, 5, 8
3, 4, 5, 8
4, 4, 4, 8
1, 5, 7, 7
2, 4, 7, 7
3, 3, 7, 7
1, 6, 6, 7
2, 5, 6, 7
3, 4, 6, 7
3, 5, 5, 7
4, 4, 5, 7
2, 6, 6, 6
3, 5, 6, 6
4, 4, 6, 6
4, 5, 5, 6
5, 5, 5, 5

此外,由于递归解决方案对非常大的分区(尽管实现起来较简单)的性能低以及RAM /堆栈的影响,我特意选择不要将其实现为递归函数。

如果有人要编译下面的帮助函数。

#include <iostream>
#include <iomanip>
#include <vector> 

unsigned int MaxPartitionVal(const unsigned int myInt, const unsigned int PartitionSize, unsigned int MaxVal)
{
    if ((myInt < 2) || (PartitionSize < 2) || (MaxVal < 1) || (PartitionSize > myInt) || (myInt > (PartitionSize*MaxVal)))  //Sanity checks
        return 0;

    unsigned int last = PartitionSize - 1;

    if (MaxVal + last > myInt)
        MaxVal = myInt - last;  //It is not always possible to start with the MaxValue. Decrease it to sth possible

    return MaxVal;
}

1 个答案:

答案 0 :(得分:1)

提供此答案的目的是希望它有用,但并不能保证它是最佳的:)。

注释

首先,一些typedef(根据需要进行更改):

using iType = uint_fast64_t; // Type of the generated indices.
using pType = unsigned; // Type of the parts in a partition.
using pSize = std::vector<pType>::size_type; // Size of a partition.

符号:

  • parts(num, size, max)num的整数分区的集合,其size部分低于或等于max
  • pparts的元素(一个std::vector,因此索引为0)。
  • getIndex(p, num, size, max)计算p的索引。
  • getPartition(index, num, size, max)计算给定index的分区。

基本思路

由于索引不必是连续的,因此我们可以这样改述问题:

  • getIndex(...)将多个整数多路复用(或压缩)为单个整数。
  • getPartition(...)将单个整数解复用(或解压缩)为原始整数。

一个常见的解决方案是:

  • 使用连续的加法和乘法进行多路复用。
  • 使用连续的欧几里得除法和模来解复用。

由于我们知道分区的每个part都会验证1 <= part && part <= max,因此第一个实现可以是:

iType getIndex(const std::vector<pType>& partition, pType max) {
    pSize i = partition.size();
    iType result = 0;
    while (i > 0) {
        i--;
        const pType iMin = 1;
        const pType iMax = max;
        pType part = partition[i];
        result = result*(iMax+1-iMin) + (part-iMin);
    }
    return result;
}

std::vector<pType> getPartition(iType index, pSize size, pType max) {
    std::vector<pType> result(size,0);
    iType currentIndex = index;
    for (pSize i = 0; i < size; i++) {
        const pType iMin = 1;
        const pType iMax = max;
        pType divider = iMax + 1 - iMin;
        result[i] = iMin + currentIndex % divider;
        currentIndex = currentIndex / divider;
    }
    return result;
}

Live demo

这有效,但是计算的索引很大。获得较低索引的技巧是使用我们正在处理分区而不是在[1; max]中的运算向量上的事实,在每次循环迭代时计算iMaxiMin的更精细的值。

具有范围约束的更好压缩方式

添加自我施加的约束:

  1. 分区从最大部分到最低部分:p[i] >= p[i+1]

我们可以为p中的parts(num, size, max)推论:

  1. p[0] >= 1 + (num-1) / size
  2. p[0] <= num + 1 - size

可以通过将p[i]放在p[1..size-1]中来将约束2和3递归应用于所有parts(num-p[0], size-1, p[0])

因此,我们可以计算出更好的iMiniMax,并将它们注入之前的实现中:

// !! Requires a sorted partition, from greatest to lowest part.
iType getIndex2(const std::vector<pType>& partition, pType max) {
    pSize size = partition.size();
    iType result = 0;
    pType currentNum = 0;

    pSize i = partition.size();
    while (i > 0) {
        i--;
        pType part = partition[i];
        currentNum = currentNum + part;
        pType iMax = currentNum+1-(size-i); // constraint 3
        if (i > 0) {
            iMax = std::min<pType>(iMax, partition[i-1]); // constraint 1
        } else {
            iMax = std::min<pType>(iMax, max);
        }
        pType iMin = 1+(currentNum-1)/(size-i); // constraint 2
        result = result*(iMax+1-iMin) + (part-iMin);
    }
    return result;
}

std::vector<pType> getPartition2(iType index, pType num, pSize size, pType max) {
    std::vector<pType> result(size,0);
    iType currentIndex = index;
    pType iMax = std::min<pType>(max, num + 1 - size); // constraint 3
    pType currentNum = num;
    for (pSize i = 0; i < size; i++) {
        pType iMin = 1+(currentNum-1)/(size-i); // constraint 2
        pType diviser = iMax+1-iMin;
        result[i] = iMin + currentIndex % diviser;
        currentIndex = currentIndex / diviser;
        currentNum = currentNum - result[i];
        iMax = std::min<pType>(result[i], currentNum + 1 - (size - i -1)); // constraint 1 & 3 for step (i+1)
    }
    return result;
}

Live demo

待办事项

  • 健全性检查:如果未对分区进行排序或分区/索引无效,则提供的实现可能会进入未定义的行为。
  • 评估iType何时溢出(并检查它是否对您足够)。我不知道索引根据numsizemax增长的速度。