All IOI entries
Competitive Programming

IOI 1990 - Problem 1: Subsets

We present two approaches: backtracking for small n and meet-in-the-middle for moderate n. Method 1: Backtracking (n 20) Enumerate subsets recursively, maintaining a running sum. At each position, either include or ex...

Source sync Apr 19, 2026
Track IOI
Year 1990
Files TeX and C++
Folder competitive_programming/ioi/1990/subsets
IOI1990TeXC++

Source-first archive entry

This page is built from the copied files in competitive_programming/ioi/1990/subsets. Edit competitive_programming/ioi/1990/subsets/solution.tex to update the written solution and competitive_programming/ioi/1990/subsets/solution.cpp to update the implementation.

The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.

Problem Statement

Rendered from the "Problem Statement" section inside solution.tex because this entry has no separate statement file.

Given a set of $n$ integers, find all subsets whose elements sum to a given target value $T$. Output each such subset.

Editorial

Rendered from the copied solution.tex file. The original TeX source remains available below.

Solution

We present two approaches: backtracking for small $n$ and meet-in-the-middle for moderate $n$.

Method 1: Backtracking ($n \le 20$)

Enumerate subsets recursively, maintaining a running sum. At each position, either include or exclude the current element. When the sum equals $T$, output the current subset.

To avoid duplicate subsets when elements repeat, sort the array and skip consecutive equal elements at the same recursion level.

Method 2: Meet in the Middle ($n \le 40$)

Split the array into halves $A$ (first $\lfloor n/2 \rfloor$ elements) and $B$ (remaining elements). Enumerate all $2^{|A|}$ subset sums from $A$ and all $2^{|B|}$ subset sums from $B$. For each sum $s$ from $B$, look up $T - s$ among the sums from $A$.

Complexity Analysis

  • Backtracking: $O(2^n)$ worst case, significantly reduced by pruning in practice.

  • Meet in the Middle: $O(2^{n/2} \cdot n)$ time, $O(2^{n/2})$ space.

Implementation: Backtracking

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int n, target;
vector<int> arr;
vector<int> current;
int count_solutions = 0;

void backtrack(int idx, int sum) {
    if (sum == target && !current.empty()) {
        cout << "{ ";
        for (int x : current) cout << x << " ";
        cout << "}" << endl;
        count_solutions++;
    }
    if (idx == n) return;
    if (sum > target && arr[idx] > 0) return; // pruning for positive values

    for (int i = idx; i < n; i++) {
        // Skip duplicates at the same level
        if (i > idx && arr[i] == arr[i-1]) continue;
        current.push_back(arr[i]);
        backtrack(i + 1, sum + arr[i]);
        current.pop_back();
    }
}

int main() {
    cin >> n >> target;
    arr.resize(n);
    for (int i = 0; i < n; i++)
        cin >> arr[i];

    sort(arr.begin(), arr.end());
    backtrack(0, 0);

    if (count_solutions == 0)
        cout << "No subsets found." << endl;
    return 0;
}

Implementation: Meet in the Middle

#include <iostream>
#include <vector>
#include <map>
using namespace std;

int main() {
    int n, target;
    cin >> n >> target;
    vector<int> arr(n);
    for (int i = 0; i < n; i++)
        cin >> arr[i];

    int half = n / 2;
    int other = n - half;

    // Generate all subset sums for the first half
    map<int, vector<vector<int>>> leftSums;
    for (int mask = 0; mask < (1 << half); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < half; i++) {
            if (mask & (1 << i)) {
                s += arr[i];
                subset.push_back(arr[i]);
            }
        }
        leftSums[s].push_back(subset);
    }

    // Generate all subset sums for the second half;
    // look up complement in leftSums
    for (int mask = 0; mask < (1 << other); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < other; i++) {
            if (mask & (1 << i)) {
                s += arr[half + i];
                subset.push_back(arr[half + i]);
            }
        }
        int need = target - s;
        auto it = leftSums.find(need);
        if (it != leftSums.end()) {
            for (auto& left : it->second) {
                if (left.empty() && subset.empty()) continue;
                cout << "{ ";
                for (int x : left) cout << x << " ";
                for (int x : subset) cout << x << " ";
                cout << "}" << endl;
            }
        }
    }

    return 0;
}

Notes

  • The backtracking solution is the most natural for the original IOI 1990 constraints (small $n$). Sorting enables both pruning and duplicate avoidance.

  • The meet-in-the-middle approach extends the practical range to about $n = 40$. Its memory usage of $O(2^{n/2})$ is the main limiting factor.

  • Both solutions output all valid subsets. If only the count is needed, the meet-in-the-middle approach can use a simple frequency map instead of storing explicit subsets.

Code

Exact copied C++ implementation from solution.cpp.

C++ competitive_programming/ioi/1990/subsets/solution.cpp

Exact copied implementation source.

Raw file
// IOI 1990 - Problem 1: Subsets
// Find all subsets of n integers that sum to target T.
// Uses meet-in-the-middle for efficiency (handles n up to ~40).
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, target;
    scanf("%d%d", &n, &target);
    vector<int> arr(n);
    for (int i = 0; i < n; i++)
        scanf("%d", &arr[i]);

    int half = n / 2;
    int other = n - half;

    // Generate all subset sums for the first half
    map<int, vector<vector<int>>> leftSums;
    for (int mask = 0; mask < (1 << half); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < half; i++) {
            if (mask & (1 << i)) {
                s += arr[i];
                subset.push_back(arr[i]);
            }
        }
        leftSums[s].push_back(subset);
    }

    // Generate all subset sums for the second half, look up complement
    for (int mask = 0; mask < (1 << other); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < other; i++) {
            if (mask & (1 << i)) {
                s += arr[half + i];
                subset.push_back(arr[half + i]);
            }
        }
        int need = target - s;
        auto it = leftSums.find(need);
        if (it != leftSums.end()) {
            for (auto& left : it->second) {
                if (left.empty() && subset.empty()) continue; // skip empty subset
                printf("{ ");
                for (int x : left) printf("%d ", x);
                for (int x : subset) printf("%d ", x);
                printf("}\n");
            }
        }
    }
    return 0;
}

Source Files

Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.

TeX write-up competitive_programming/ioi/1990/subsets/solution.tex

Exact copied write-up source.

Raw file
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage[margin=2.5cm]{geometry}
\usepackage{xcolor}

\lstset{
  language=C++,
  basicstyle=\ttfamily\small,
  keywordstyle=\color{blue}\bfseries,
  commentstyle=\color{green!50!black},
  stringstyle=\color{red!70!black},
  numbers=left,
  numberstyle=\tiny\color{gray},
  breaklines=true,
  frame=single,
  tabsize=4,
  showstringspaces=false
}

\title{IOI 1990 -- Problem 1: Subsets}
\author{Solution}
\date{}

\begin{document}
\maketitle

\section{Problem Statement}

Given a set of $n$ integers, find all subsets whose elements sum to a given
target value $T$. Output each such subset.

\section{Solution}

We present two approaches: backtracking for small $n$ and meet-in-the-middle
for moderate $n$.

\subsection{Method 1: Backtracking ($n \le 20$)}

Enumerate subsets recursively, maintaining a running sum. At each position,
either include or exclude the current element. When the sum equals $T$,
output the current subset.

To avoid duplicate subsets when elements repeat, sort the array and skip
consecutive equal elements at the same recursion level.

\subsection{Method 2: Meet in the Middle ($n \le 40$)}

Split the array into halves $A$ (first $\lfloor n/2 \rfloor$ elements) and
$B$ (remaining elements). Enumerate all $2^{|A|}$ subset sums from $A$ and
all $2^{|B|}$ subset sums from $B$. For each sum $s$ from $B$, look up
$T - s$ among the sums from $A$.

\section{Complexity Analysis}

\begin{itemize}
  \item \textbf{Backtracking:} $O(2^n)$ worst case, significantly reduced by
    pruning in practice.
  \item \textbf{Meet in the Middle:} $O(2^{n/2} \cdot n)$ time,
    $O(2^{n/2})$ space.
\end{itemize}

\section{Implementation: Backtracking}

\begin{lstlisting}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int n, target;
vector<int> arr;
vector<int> current;
int count_solutions = 0;

void backtrack(int idx, int sum) {
    if (sum == target && !current.empty()) {
        cout << "{ ";
        for (int x : current) cout << x << " ";
        cout << "}" << endl;
        count_solutions++;
    }
    if (idx == n) return;
    if (sum > target && arr[idx] > 0) return; // pruning for positive values

    for (int i = idx; i < n; i++) {
        // Skip duplicates at the same level
        if (i > idx && arr[i] == arr[i-1]) continue;
        current.push_back(arr[i]);
        backtrack(i + 1, sum + arr[i]);
        current.pop_back();
    }
}

int main() {
    cin >> n >> target;
    arr.resize(n);
    for (int i = 0; i < n; i++)
        cin >> arr[i];

    sort(arr.begin(), arr.end());
    backtrack(0, 0);

    if (count_solutions == 0)
        cout << "No subsets found." << endl;
    return 0;
}
\end{lstlisting}

\section{Implementation: Meet in the Middle}

\begin{lstlisting}
#include <iostream>
#include <vector>
#include <map>
using namespace std;

int main() {
    int n, target;
    cin >> n >> target;
    vector<int> arr(n);
    for (int i = 0; i < n; i++)
        cin >> arr[i];

    int half = n / 2;
    int other = n - half;

    // Generate all subset sums for the first half
    map<int, vector<vector<int>>> leftSums;
    for (int mask = 0; mask < (1 << half); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < half; i++) {
            if (mask & (1 << i)) {
                s += arr[i];
                subset.push_back(arr[i]);
            }
        }
        leftSums[s].push_back(subset);
    }

    // Generate all subset sums for the second half;
    // look up complement in leftSums
    for (int mask = 0; mask < (1 << other); mask++) {
        int s = 0;
        vector<int> subset;
        for (int i = 0; i < other; i++) {
            if (mask & (1 << i)) {
                s += arr[half + i];
                subset.push_back(arr[half + i]);
            }
        }
        int need = target - s;
        auto it = leftSums.find(need);
        if (it != leftSums.end()) {
            for (auto& left : it->second) {
                if (left.empty() && subset.empty()) continue;
                cout << "{ ";
                for (int x : left) cout << x << " ";
                for (int x : subset) cout << x << " ";
                cout << "}" << endl;
            }
        }
    }

    return 0;
}
\end{lstlisting}

\section{Notes}

\begin{itemize}
  \item The backtracking solution is the most natural for the original IOI
    1990 constraints (small $n$). Sorting enables both pruning and duplicate
    avoidance.
  \item The meet-in-the-middle approach extends the practical range to about
    $n = 40$. Its memory usage of $O(2^{n/2})$ is the main limiting factor.
  \item Both solutions output all valid subsets. If only the count is needed,
    the meet-in-the-middle approach can use a simple frequency map instead of
    storing explicit subsets.
\end{itemize}

\end{document}