【问题标题】:Variant of Subset-Sum子集和的变体
【发布时间】:2016-09-18 05:58:40
【问题描述】:

给定3 正整数n, k, and sum,精确找到k 不同元素的数量a_i,其中
a_i \in S, 1 <= i <= k, and a_i \neq a_j for i \neq j
而且,S 是集合
S = {1, 2, 3, ..., n}
这样
\sum_{i=1}^{k}{a_i} = sum
由于指数复杂性,我不想应用蛮力(检查所有可能的组合)来解决问题。有人可以提示我解决此问题的另一种方法吗?另外,我们如何利用集合S 已排序的事实? 在这个问题中是否可能有O(k) 的复杂性?

【问题讨论】:

  • 欢迎来到stackoverflow。在获得答案之前,您应该表现出一些自己的努力。你试过什么了?你能展示代码示例吗?

标签: algorithm set subset-sum


【解决方案1】:

一个如何利用1..n设置属性的想法:

a开始的自然行的k个连续成员的总和是

sum = k*(2*a + (k-1))/2

为了得到所需s的子序列之和,我们可以解决

a >= s/k - k/2 + 1/2
or 
a <= s/k - k/2 + 1/2

比较 ssum 的值并进行更正。

例如,有s=173n=40k=5,我们可以找到

a <= 173/5 - 5/2 + 1/2 = 32.6

对于起始编号 32,我们有序列 32,33,34,35,36sum = 170,对于 3 更正,我们可以将 36 更改为 39,或者将 34,35,36 更改为 35,36,37 等等。

似乎使用这种方法我们得到了 O(1) 复杂度(当然,可能存在一些我确实错过的细微之处)

【讨论】:

    【解决方案2】:

    可以修改pseudo-polynomial algorithm for subset sum

    准备一个维度为k X sum的矩阵P,并将所有元素初始化为0。P[p, q] == 1的含义 是有一个 p 个数和 q 的子集,而 P[p, q] == 0 表示这样的尚未找到子集。

    现在迭代 i = 1, ..., n。在每次迭代中:

    1. 如果i ≤ sum,设置P[1, i] = 1(有一个大小为1的子集达到i em>)。

    2. 对于任何条目 P[p, q] == 1,您现在知道 P[p + 1, q + i] 现在应该是1也。如果(p + 1, q + i)在矩阵的边界内,设P[p + 1, q + i] = 1

    最后,检查是否P[k, sum] == 1

    假设所有整数数学运算都是常数,复杂度是Θ(n2 sum)

    【讨论】:

      【解决方案3】:

      有一个 O(1)(可以这么说)的解决方案。接下来是@MBo 对这个想法的足够正式(我希望)的发展。

      假设S 是所有整数的集合并找到最小解就足够了。解决方案K 小于K' iff max(K) &lt; max(K')。如果max(K) &lt;= n,那么K也是原问题的解决方案;否则,原问题无解。

      所以我们忽略n 并找到K,这是一个最小的解决方案。让g = max(K) = ceil(sum/k + (k - 1)/2)s = g + (g-1) + (g-2) + ... (g-k+1)s' = (g-1) + (g-2) + ... + (g-k)。也就是说,s's 下移 1。注意 s' = s - k

      显然是s &gt;= sum 和(因为K 最小)s' &lt; sum

      如果s == sum 解决方案是K,我们就完成了。否则考虑集合K+ = {g, g-1, ..., g-k}。我们知道\sum(K+ \setminus {g}) &lt; sum\sum(K+ \setminus {g-k}) &gt; sum,因此,有一个K+ 的元素g_i 使得\sum (K+ \setminus {g_i}) = sum。解决办法是K+ \setminus {\sum(K+)-sum}

      4 个整数 a, b, c, d 形式的解可以在 O(1) 中计算,其中实际集合被理解为 [a..b] \setunion [c..d]

      【讨论】:

        【解决方案4】:
        #include <stdio.h>
        #include <stdlib.h>
        #include <stdbool.h>
        
        unsigned long int arithmeticSum(unsigned long int a, unsigned long int k, unsigned long int n, unsigned long int *A);
        void printSubset(unsigned long int k, unsigned long int *A);
        
        int main(void)
        {
            unsigned long int n, k, sum;
            // scan the respective values of sum, n, and k
            scanf("%lu %lu %lu", &sum, &n, &k);
            // find the starting element using the formula for the sum of an A.P. having 'k' terms
            // starting at 'a', common difference 'd' ( = 1 in this problem), having 'sum' = sum
            // sum = [k/2][2*a + (k-1)*d]
            unsigned long startElement = (long double)sum/k - (long double)k/2 + (long double)1/2;
            // exit if the arithmetic progression formed at the startElement is not within the required bounds
            if(startElement < 1 || startElement + k - 1 > n)
            {
                printf("-1\n");
                return 0;
            }
            // we now work on the k-element set [startElement, startElement + k - 1]
            // create an array to store the k elements
            unsigned long int *A = malloc(k * sizeof(unsigned long int));
            // calculate the sum of k elements in the arithmetic progression [a, a + 1, a + 2, ..., a + (k - 1)]
            unsigned long int currentSum = arithmeticSum(startElement, k, n, A);
            // if the currentSum is equal to the required sum, then print the array A, and we are done
            if(currentSum == sum)
            {
                printSubset(k, A);
            }
            // we enter into this block only if currentSum < sum
            // i.e. we need to add 'something' to the currentSum in order to make it equal to sum
            // i.e. we need to remove an element from the k-element set [startElement, startElement + k - 1]
            // and replace it with an element of higher magnitude
            // i.e. we need to replace an element in the set [startElement, startElement + k - 1] and replace
            // it with an element in the range [startElement + k, n]
            else
            {
                long int j;
                bool done;
                // calculate the amount which we need to add to the currentSum
                unsigned long int difference = sum - currentSum;
                // starting from A[k-1] upto A[0] do the following...
                for(j = k - 1, done = false; j >= 0; j--)
                {
                    // check if adding the "difference" to A[j] results in a number in the range [startElement + k, n]
                    // if it does then replace A[j] with that element, and we are done
                    if(A[j] + difference <= n && A[j] + difference > A[k-1])
                    {
                        A[j] += difference;
                        printSubset(k, A);
                        done = true;
                        break;
                    }
                }
                // if no such A[j] is found then, exit with fail
                if(done == false)
                {
                    printf("-1\n");
                }
            }
            return 0;
        }
        
        unsigned long int arithmeticSum(unsigned long int a, unsigned long int k, unsigned long int n, unsigned long int *A)
        {
            unsigned long int currentSum;
            long int j;
            // calculate the sum of the arithmetic progression and store the each member in the array A
            for(j = 0, currentSum = 0; j < k; j++)
            {
                A[j] = a + j;
                currentSum +=  A[j];
            }
            return currentSum;
        }
        
        void printSubset(unsigned long int k, unsigned long int *A)
        {
            long int j;
            for(j = 0; j < k; j++)
            {
                printf("%lu ", A[j]);
            }
            printf("\n");
        }
        

        【讨论】:

        • 此解决方案基于MBo 的回答。谢谢
        猜你喜欢
        • 2018-06-02
        • 1970-01-01
        • 2020-03-20
        • 1970-01-01
        • 2015-12-24
        • 1970-01-01
        • 1970-01-01
        • 2010-09-27
        • 1970-01-01
        相关资源
        最近更新 更多