5

I have an array of size n of integer values and a given number S.

1<=n<=30

I want to find the total number of sub-sequences such that for each sub-sequences elements sum is less than S. For example: let n=3 , S=5and array's elements be as {1,2,3}then its total sub-sequences be 7 as-

{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}

but, required sub sequences is:

{1},{2},{3},{1,2},{1,3},{2,3}

that is {1,2,3}is not taken because its element sum is (1+2+3)=6which is greater than S that is 6>S. Others is taken because, for others sub-sequences elements sum is less than S. So, total of possible sub-sequences be 6. So my answer is count, which is6.

I have tried recursive method but its time complexity is 2^n. Please help us to do it in polynomial time.

Enigma
  • 179
  • 2
  • 12
  • 1
    https://en.wikipedia.org/wiki/Knapsack_problem In the worst case you probably still need 2^n time. – Petar Petrovic May 09 '17 at 07:58
  • Boy I'd love a polynomial time algorithm too. A quick million dollars – Passer By May 09 '17 at 08:18
  • The above problem can be reduced to finding the number of ways to get sum - 1, 2, 3, ..... S-1, using the array elements. – GAURANG VYAS May 09 '17 at 08:20
  • @Petar Petrovic, only one of each item seems to be allowed, which is not the knapsack problem? – keith May 09 '17 at 08:22
  • @keith, I think it should be a special case called 0/1 knapsack problem – Petar Petrovic May 09 '17 at 08:34
  • @PetarPetrovic it's called [subset sum](https://en.m.wikipedia.org/wiki/Subset_sum_problem) – Passer By May 09 '17 at 08:43
  • @PasserBy I just realize that maybe it is not original knapsack. But this task is not about finding a set of items with zero sum/fix sum?(Which is what subset sum want to do) – Petar Petrovic May 09 '17 at 08:54
  • @PetarPetrovic Find the number of ways to sum to at most S, and the number of way to sum to at most S-1. The difference is the number of ways to sum to exactly S. – n. m. could be an AI May 09 '17 at 09:13
  • @n.m. Yes, but then this is reducing the problem to subset sum? – Petar Petrovic May 09 '17 at 09:16
  • @PetarPetrovic it's the other way around. You reduce problem A to problem B when you convert a solution for B to work for A. So you can reduce subset-sum to this problem. – n. m. could be an AI May 09 '17 at 09:27
  • 2
    If you can wait for one more week, you will find an efficient solution in the editorial of this problem of an ongoing contest: https://www.codechef.com/MAY17/problems/CHEFCODE. The question there involves products, but the idea is the same. – danbanica May 09 '17 at 09:33
  • Maximum value of S? – Nir Friedman May 09 '17 at 09:37
  • @n.m. Yes, "A polynomial-time Turing reduction from a problem A to a problem B is an algorithm that solves problem A using a polynomial number of calls to a subroutine for problem B" I just copy this from wiki. So it should be reducing to subset sum since you are calling subset sum solver to solve the op's problem? Anyway my idea is that you can also do this kind of reduction to knapsack, so following the logic you can also say that op's problem is knapsack problem – Petar Petrovic May 09 '17 at 09:37
  • 1
    @PetarPetrovic "you are calling subset sum solver to solve the op's problem?" No, I do just the opposite. I use the alleged polynomial solution to the user's problem to solve subset-sum in polynomial time. You can reduse any NP-complete problem to any other, so no surprise here. – n. m. could be an AI May 09 '17 at 09:48
  • Possible duplicate of [Subset Sum algorithm](http://stackoverflow.com/questions/4355955/subset-sum-algorithm) – Passer By May 09 '17 at 13:00

2 Answers2

2

You can solve this in reasonable time (probably) using the pseudo-polynomial algorithm for the knapsack problem, if the numbers are restricted to be positive (or, technically, zero, but I'm going to assume positive). It is called pseudo polynomial because it runs in nS time. This looks polynomial. But it is not, because the problem has two complexity parameters: the first is n, and the second is the "size" of S, i.e. the number of digits in S, call it M. So this algorithm is actually n 2^M.

To solve this problem, let's define a two dimensional matrix A. It has n rows and S columns. We will say that A[i][j] is the number of sub-sequences that can be formed using the first i elements and with a maximum sum of at most j. Immediately observe that the bottom-right element of A is the solution, i.e. A[n][S] (yes we are using 1 based indexing).

Now, we want a formula for A[i][j]. Observe that all subsequences using the first i elements either include the ith element, or do not. The number of subsequences that don't is just A[i-1][j]. The number of subsequences that do is just A[i-1][j-v[i]], where v[i] is just the value of the ith element. That's because by including the ith element, we need to keep the remainder of the sum below j-v[i]. So by adding those two numbers, we can combine the subsequences that do and don't include the jth element to get the total number. So this leads us to the following algorithm (note: I use zero based indexing for elements and i, but 1 based for j):

std::vector<int> elements{1,2,3};
int S = 5;
auto N = elements.size();
std::vector<std::vector<int>> A;
A.resize(N);
for (auto& v : A) {
    v.resize(S+1);  // 1 based indexing for j/S, otherwise too annoying
}

// Number of subsequences using only first element is either 0 or 1
for (int j = 1; j != S+1; ++j) {
    A[0][j] = (elements[0] <= j);
}

for (int i = 1; i != N; ++i) {
    for (int j = 1; j != S+1; ++j) {
        A[i][j] = A[i-1][j];  // sequences that don't use ith element
        auto leftover = j - elements[i];
        if (leftover >= 0) ++A[i][j];  // sequence with only ith element, if i fits
        if (leftover >= 1) {  // sequences with i and other elements
            A[i][j] += A[i-1][leftover];
        }
    }
}

Running this program and then outputting A[N-1][S] yields 6 as required. If this program does not run fast enough you can significantly improve performance by using a single vector instead of a vector of vectors (and you can save a bit of space/perf by not wasting a column in order to 1-index, as I did).

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
-1

Yes. This problem can be solved in pseudo-polynomial time.

Let me redefine the problem statement as "Count the number of subsets that have SUM <= K".

Given below is a solution that works in O(N * K),

where N is the number of elements and K is the target value.

int countSubsets (int set[], int K) {
    int dp[N][K];

    //1. Iterate through all the elements in the set.
    for (int i = 0; i < N; i++) {
        dp[i][set[i]] = 1;

        if (i == 0) continue;

        //2. Include the count of subsets that doesn't include the element set[i]
        for (int k = 1; k < K; k++) {
            dp[i][k] += dp[i-1][k];
        }

        //3. Now count subsets that includes element set[i]
        for (int k = 0; k < K; k++) {
            if (k + set[i] >= K) {
                break;
            }
            dp[i][k+set[i]] += dp[i-1][k];
        }
    }
    //4. Return the sum of the last row of the dp table.
    int count = 0;
    for (int k = 0; k < K; k++) {
        count += dp[N-1][k];
    }
    // here -1 is to remove the empty subset
    return count - 1;
}       
XOR
  • 314
  • 5
  • 11
  • Um.. the code won't compile because you're missing a bracket at `//3.`. – Passer By May 09 '17 at 12:06
  • Also, this is not polynomial time, it is pseudo-polynomial, which is in fact, exponential – Passer By May 09 '17 at 12:14
  • This solution works in O(N*K) as shown. The performance will be great for reasonable values of K. In any case, it is more efficient than a plain recursive algorithm. – XOR May 09 '17 at 12:22
  • 1
    First off, it's not strictly faster than brute force. It's better in terms of one complexity parameter but worse in terms of the other. Second, your code is not valid c++. Variable length arrays are not part of c++. – Nir Friedman May 09 '17 at 13:15