cppAcademy
← Learning PathLESSON 9 / 30 · DYNAMIC PROGRAMMING · 45 MIN✕

Subset Sum

Subset sum asks whether some 0/1 subset reaches an exact target, not the best value under a capacity. Boolean states merge histories with the same sum. We also recover one witness using frozen first-reach parent links. Nonnegative inputs make a bounded target array and descending update order possible; arbitrary negative values need a different range representation.

Prerequisites: 0/1 Knapsack; reconstruction; boolean states.

Build the idea

  1. Recognize the repeated subproblems

    Complete search tries 2^n subsets. When target S is moderate and values are nonnegative, there are only S+1 relevant totals; sums above S can never return downward. Constraints n≤1000, S≤100000 permit an O(nS) pseudo-polynomial method. With negative values, discarding larger sums would be unsound because a later negative could bring them back.

  2. Define the state

    reachable[s] means some subset of processed entries totals exactly s. item[s] and previous[s] record the input index used and its prior sum when s becomes reachable for the first time. The zero sum is reachable by the empty subset, including when the input contains zeros. We need just one witness, not the number of subsets.

  3. Derive the transition

    For each value x, scan s downward from S to x. If s was not reachable but s−x was, set it reachable and freeze parent data. Descending order ensures the predecessor has not newly used this item. An x>S causes no updates. For x=0 no new false state can become true from itself, so no zero-length parent cycle is created.

    reachable[s]=reachable[s]ORreachable[s−x]

  4. Base cases and computation order

    Initialize reachable[0]=true and all other entries false. Read all items even if the target is already reachable, keeping the input contract simple. If target remains false, print NO. Otherwise follow previous from S until zero, collect item IDs and reverse. Frozen parent states were reached with strictly earlier items; zero values are never needed to create a new positive witness sum.

  5. Correctness and invariant

    For each item, a target subset either excludes it or removes it to a subset of earlier entries totaling s−x. Boolean OR therefore covers exactly all feasible subsets. First-reach parent data points to that earlier-layer witness and is never overwritten, so its chain uses no input item twice. This freezing matters: an arbitrary mutable parent scheme on compressed DP can reference inconsistent layers.

  6. Work the example

    Values [3,34,4,5], target 9: after the first item sums {0,3} are reachable;34 is too large. Adding 4 yields {0,3,4,7}. Adding 5 reaches 9 from 4, storing item 4 and predecessor 4, whose parent uses item 3. Output YES and IDs 3,4 witnesses 4+5.

    processed valuesrelevant reachable sums
    none0
    30,3
    3,34,40,3,4,7
    all0,3,4,5,7,8,9

C++17 implementation

This is a complete program. Compile with C++17 and enter the input described below.

#include<iostream>
#include<vector>
#include<algorithm>
int main(){
    int n,target;
    std::cin>>n>>target;
    std::vector<bool>reachable(target+1);
    std::vector<int>item(target+1,-1),previous(target+1,-1);
    reachable[0]=true;
    for(int i=1;i<=n;++i){
        int x;
        std::cin>>x;
        for(int s=target;s>=x;--s){
            if(!reachable[s]&&reachable[s-x]){
                reachable[s]=true;
                item[s]=i;
                previous[s]=s-x;
            }
        }
    } // Freeze the first parent for each sum.
    if(!reachable[target]){
        std::cout<<"NO\n";
        return 0;
    }
    std::vector<int>chosen;
    for(int s=target;s>0;s=previous[s]){
        chosen.push_back(item[s]);
    }
    std::reverse(chosen.begin(),chosen.end());
    std::cout<<"YES\n"<<chosen.size();
    for(int id:chosen){
        std::cout<<' '<<id;
    }
    std::cout<<'\n';
}

Complexity analysis

O(nS) time, O(S) reachability/parent space and O(n) worst-case witness storage. The logical item layers are compressed through descending sums. Bitset shifts can accelerate decision-only nonnegative variants, but direct reconstruction needs more care.

Common mistakes and edge cases. Descending sums prevent reuse. Do not interpret false initialization as a minimum-cost infinity. Negative numbers invalidate the restricted 0..S state range. Freeze first-reach parents to preserve their item-layer meaning. Target zero is valid and should not force a nonempty subset.

Example input and output

Read n S (0≤n≤1000,0≤S≤100000), then n values in 0..10^6. Print NO, or YES followed by witness count and 1-based item IDs. Empty target permits an empty subset.

Input

4 9
3 34 4 5

Output

YES
2 3 4

Values [3,34,4,5], target 9: after the first item sums {0,3} are reachable;34 is too large. Adding 4 yields {0,3,4,7}. Adding 5 reaches 9 from 4, storing item 4 and predecessor 4, whose parent uses item 3. Output YES and IDs 3,4 witnesses 4+5.

Practical applications

Use exact nonnegative sum feasibility and partition reductions. Meet-in-the-middle helps when n is small and S huge; offset/range maps may support negative inputs under suitable bounds. Counting subsets requires counts and multiplicity rules rather than a boolean.

Check your understanding

Why can values above S be ignored in this implementation?

Put it into practice

Test zeros, duplicate values, S=0 and an unreachable positive target.

Show a hint

Verify witness IDs are distinct and their values sum exactly to S.