3-PARTITION问题

时间:2011-01-26 10:51:04

标签: algorithm dynamic-programming partition-problem

这是另一个动态编程问题(Vazirani ch6

  

考虑以下3-PARTITION   问题。鉴于整数a1 ... an,我们   想确定是否是   可以将{1 ... n}划分为   三个不相交的子集I,J,K等   该

     

sum(I)= sum(J)= sum(K)= 1/3 * sum(ALL)   

     

例如,输入(1; 2; 3; 4; 4;   5; 8)答案是肯定的,因为那里   是分区(1; 8),(4; 5),(2;   3; 4)。另一方面,输入   (2; 2; 3; 5)答案是否定的。设计   并分析动态编程   运行于3-PARTITION的算法   时间多项式在n和(Sum a_i)

我该如何解决这个问题?我知道2分区但仍然无法解决它

7 个答案:

答案 0 :(得分:16)

对于3组案例,很容易推广2组解决方案。

在原始版本中,您创建一个布尔值sums数组,其中sums[i]告诉您是否可以使用集合中的数字来达到总和i。然后,一旦创建了数组,您就会看到sums[TOTAL/2]是否为true

既然你说你已经知道旧版本了,我只会描述它们之间的区别。

在3分区的情况下,保留布尔sums数组,其中sums[i][j]告诉第一组是否可以有i和第二 - 和j。然后,一旦创建了数组,您就会看到sums[TOTAL/3][TOTAL/3]是否为true

如果原始复杂度为O(TOTAL*n),则此处为O(TOTAL^2*n) 它可能不是最严格意义上的多项式,但原始版本也不是严格多项式的:)

答案 1 :(得分:6)

我认为通过减少就是这样:

将2分区缩减为3分区:

设S为原始集合,A为其总和,然后让S'= union({A / 2},S)。 因此,在集合S'上执行3分区产生三组X,Y,Z。 在X,Y,Z中,其中一个必须是{A / 2},比如说它是Z,那么X和Y是一个2分区。 S'上3分区的见证是S上2分区的见证,因此2分区缩减为3分区。

答案 2 :(得分:2)

如果这个问题是可以解决的;那么sum(ALL)/3必须是整数。任何解决方案都必须SUM(J) + SUM(K) = SUM(I) + sum(ALL)/3。这代表了concat(ALL, {sum(ALL)/3})上的2分区问题的解决方案。

你说你有一个2分区实现:用它来解决那个问题。然后(至少)两个分区中的一个将包含数字sum(ALL)/3 - 删除该分区中的数字,并且您已找到I。对于其他分区,再次运行2分区,从J分割K;毕竟,JK必须相等。

编辑: 此解决方案可能不正确 - 连接集的2分区将有多个解决方案(I,J,K各自至少有一个) - 但是,如果有其他解决方案,那么“另一方”可能不包括I,J,K中的两个的并集,并且可能根本不可拆分。你需要实际思考,我担心: - )。

尝试2: 迭代多重集,维护以下映射:R(i,j,k) :: Boolean表示当前迭代中数字是否允许划分为三个多重集合,其总和为ijk。即,对于下一个州R(i,j,k)中的任何n和下一个号码R',它包含R'(i+n,j,k)R'(i,j+n,k)以及R'(i,j,k+n)。请注意,复杂性(根据excersize)取决于输入数字的幅度;这是伪多项式时间算法。尼基塔的解决方案在概念上与此解决方案相似但效率更高,因为它不跟踪第三组的总和:这是不必要的,因为您可以轻松地计算它。

答案 3 :(得分:1)

正如我在另一个类似的问题中回答的那样,C ++实现看起来像这样:

[](const auto& lhs, const auto& rhs) {
    return lhs.first == rhs.first ? lhs.second < rhs.second : lhs.first < rhs.first;
}

答案 4 :(得分:0)

假设您要对$X = {x_1, ..., x_n}$分区中的集$k$进行分区。 创建一个$ n \次k $表。假设成本$M[i,j]$是$ j $分区中$i$个元素的最大总和。只需递归使用以下最优性标准来填充它:

M[n,k] = min_{i\leq n}  max ( M[i, k-1], \sum_{j=i+1}^{n} x_i ) 

Using these initial values for the table: 

M[i,1] = \sum_{j=1}^{i} x_i  and  M[1,j] = x_j  

The running time is $O(kn^2)$ (polynomial )

答案 5 :(得分:0)

你真的想要Korf的完整Karmarkar-Karp算法(http://ac.els-cdn.com/S0004370298000861/1-s2.0-S0004370298000861-main.pdfhttp://ijcai.org/papers09/Papers/IJCAI09-096.pdf)。给出了对三分区的推广。鉴于问题的复杂性,算法速度惊人,但需要一些实现。

KK的基本思想是确保相似大小的大块出现在不同的分区中。一组成对的块,然后可以将其视为一个较小的块,其大小等于可以正常放置的大小差异:通过递归执行此操作,最终会得到容易放置的小块。然后,我们对块组进行双色处理,以确保处理相反的位置。 3分区的扩展有点复杂。 Korf扩展是使用KK顺序的深度优先搜索来查找所有可能的解决方案或快速找到解决方案。

答案 6 :(得分:0)

创建一个三维数组,其中size是元素的个数,part等于所有元素的总和除以3。所以数组的每个单元格[ seq][sum1][sum2] 告诉您是否可以使用给定数组 A[] 中的最大 seq 元素创建 sum1 和 sum2。所以计算数组的所有值,结果将在单元格数组中[使用所有元素][所有元素的总和/3][所有元素的总和/3],如果你可以创建两个不交叉等于和/3的集合,有将是第三盘。

检查逻辑:将A[seq]元素排除到第三个和(不存储),检查没有元素的单元格是否有相同的两个和;或包含到 sum1 - 如果可以得到两个没有 seq 元素的集合,其中 sum1 比元素 seq A[seq] 的值小,并且 sum2 没有改变;或者像以前一样包含 sum2 检查。

int partition3(vector<int> &A)
{
    int part=0;
    for (int a : A)
        part += a;
    if (part%3)
        return 0;
    int size = A.size()+1;
    part = part/3+1;
    bool array[size][part][part];
    //sequence from 0 integers inside to all inside
    for(int seq=0; seq<size; seq++)
        for(int sum1=0; sum1<part; sum1++)
            for(int sum2=0;sum2<part; sum2++) {
                bool curRes;
                if (seq==0)
                    if (sum1 == 0 && sum2 == 0)
                        curRes = true;
                    else
                        curRes= false;
                else {
                    int curInSeq = seq-1;
                    bool excludeFrom = array[seq-1][sum1][sum2];
                    bool includeToSum1 = (sum1>=A[curInSeq]
                                          && array[seq-1][sum1-A[curInSeq]][sum2]);
                    bool includeToSum2 = (sum2>=A[curInSeq]
                                          && array[seq-1][sum1][sum2-A[curInSeq]]);
                    curRes = excludeFrom || includeToSum1 || includeToSum2;
                }
                array[seq][sum1][sum2] = curRes;
            }
    int result = array[size-1][part-1][part-1];
    return result;
}