11. Brute force
Brute force means trying every possibility and then picking the ones that satisfy the requirement. It is often underestimated, yet in OSN brute force has three uses: it is the full answer for problems with small constraints, a partial answer for problems with large constraints, and a reference for testing smarter solutions.
Steps for building a brute force
- Decide what to try — every element? every pair? every subset? every ordering?
- Count how many possibilities there are.
- Compare with the constraints table in Chapter 10.
- If it fits, write the loops. If not, look for an observation that cuts down the number of possibilities.
Steps 2 and 3 must not be skipped. A brute force that was not counted first is the most common cause of a Time Limit Exceeded verdict.
The number of possibilities for the forms covered in this chapter:
| What is tried | Number of possibilities | Safe up to roughly |
|---|---|---|
| every element | n |
n = 10,000,000 |
| every pair | n(n−1)/2 | n = 5,000 |
| every triple | n³/6 | n = 500 |
| every subset | 2ⁿ | n = 22 |
| every ordering | n! | n = 10 |
How fast the last two rows grow:
n |
2ⁿ | n! |
|---|---|---|
| 10 | 1,024 | 3,628,800 |
| 15 | 32,768 | 1.3 × 10¹² |
| 20 | 1,048,576 | 2.4 × 10¹⁸ |
| 25 | 33,554,432 | 1.6 × 10²⁵ |
| 30 | 1,073,741,824 | 2.7 × 10³² |
That is why the problem constraints are a hint that almost never
lies: n ≤ 20 almost always means "try every subset", and
n ≤ 10 almost always means "try every ordering".
Form 1: every element
There are n possibilities, so O(n). This is the simplest
brute force — and it is often already the best solution.
// find the largest element
int besar = a[0];
for (int i = 1; i < n; i++) if (a[i] > besar) besar = a[i];Form 2: every pair
There are n(n−1)/2 possibilities, so O(n²). Safe up to about
n = 5,000.
// how many pairs sum to k?
int banyak = 0;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
if (a[i] + a[j] == k) banyak++;Note j = i + 1, not j = 0. This way every
pair is counted once and no element is paired with itself. For
n = 4, exactly six pairs are checked:
i |
j tried |
pairs |
|---|---|---|
| 0 | 1, 2, 3 | (0,1) (0,2) (0,3) |
| 1 | 2, 3 | (1,2) (1,3) |
| 2 | 3 | (2,3) |
| 3 | — | — |
If j starts from 0, the count becomes 16:
every pair is counted twice and every element is paired with itself. In
a "how many pairs" problem, each of those two mistakes changes the
answer.
Open
exercises/11/01-semua-pasangan.cpp.
Change j = i + 1 to j = 0, run it on the same
data, and compare the count with the earlier one.
Form 3: every subset
If each item has only two states — taken or not — then n
items have 2ⁿ possibilities. Safe up to about n = 22.
The way to write it uses binary numbers: a number mask
from 0 to 2ⁿ − 1 represents one choice, and
bit i says whether item i is taken.
for (int mask = 0; mask < (1 << n); mask++) {
long long jumlah = 0;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) jumlah += a[i]; // bit i is set
}
if (jumlah == target) banyak++;
}| Expression | Meaning |
|---|---|
1 << n |
2 to the power n |
mask & (1 << i) |
nonzero if bit i is set |
How to read it: every integer is at the same time a list of "yes/no"
answers. For n = 3 and items a = {5, 7, 2},
the eight values of mask represent the eight choices:
mask |
binary | items taken | sum |
|---|---|---|---|
| 0 | 000 | — | 0 |
| 1 | 001 | a[0] |
5 |
| 2 | 010 | a[1] |
7 |
| 3 | 011 | a[0], a[1] |
12 |
| 4 | 100 | a[2] |
2 |
| 5 | 101 | a[0], a[2] |
7 |
| 6 | 110 | a[1], a[2] |
9 |
| 7 | 111 | all of them | 14 |
Note that the rightmost bit is bit 0, that is, the
first item. The binary column is exactly binary counting from 0 to 7, so
the loop
for (int mask = 0; mask < (1 << n); mask++) is
guaranteed to visit every possibility exactly once — nothing is skipped
and nothing is duplicated. That is the advantage of this method over
writing n levels of nested loops, which cannot even be
written when n is only known while the program runs.
1 << n is computed as an int. Up to
n = 30 it is still safe, but 1 << 31
already overflows. If n can be that large, write
1LL << n — although in practice n = 31
is already far too slow to try everything.
The number of steps is n · 2ⁿ. For n = 20 that is about
21 million — still safe. For n = 30 it becomes 32 billion —
not safe.
Open
exercises/11/02-himpunan-bagian.cpp.
Run it for n = 4 and see that the output prints all sixteen
subsets. Then change n to 20 and measure the time.
Form 4: every ordering
If what you are looking for is an arrangement (who goes first, which
route is taken), the number of possibilities is n!. Safe only up to
about n = 10 (10! = 3.6 million).
next_permutation from <algorithm>
rearranges the contents into the next ordering in dictionary
order, and returns false once there is no ordering
after it. For {0, 1, 2} the orderings it produces are:
012 021 102 120 201 210
Because it only steps "forward", the data must start out sorted in ascending order — otherwise some orderings never appear:
#include <algorithm>
int p[] = {0, 1, 2, 3};
sort(p, p + n);
do {
// p now holds one ordering
} while (next_permutation(p, p + n));Pruning the possibilities
A brute force does not have to literally check everything. Two ways to prune:
Stop early. As soon as the answer is certain, exit.
for (long long i = 2; i * i <= n; i++)
if (n % i == 0) return false; // no need to continueCompute the rest instead of searching for it. If two of three values have already been chosen, the third can often be computed directly — one nested loop disappears.
// a + b + c = k, count the triples: two loops are enough
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++) {
int c = k - a[i] - a[j];
// then look up c in a fast way
}Open
exercises/11/03-semua-urutan.cpp.
Replace the line that fills p together with its
sort line with p[0] = 2; p[1] = 0; p[2] = 1;,
then run it for n = 3. Only two orderings are printed, not
six: next_permutation can only step forward from its
starting arrangement.
Pruning turns O(n³) into O(n²), but it does not turn O(2ⁿ) into O(n²). If the problem constraints are far beyond the reach of brute force, what is needed is not pruning but a different idea — greedy (Chapter 12) or dynamic programming (Chapter 13).
Partial scores
OSN problems often have tiered constraints, for example:
- Subtask 1 (30 points):
n ≤ 20 - Subtask 2 (30 points):
n ≤ 2,000 - Subtask 3 (40 points):
n ≤ 200,000
An O(2ⁿ) brute force solves subtask 1. An O(n²) brute force solves subtasks 1 and 2. Sixty points from a "dumb" solution is far better than zero points from a smart solution that never got finished.
Contest strategy: write the brute force first, submit it, secure the points, and only then think about the full solution. That brute force is also immediately useful as a reference (Chapter 10) when hunting for bugs in the fast solution.
Complete example: subset sum
Given
nitems with weightsa[1..n]and a targetk. How many ways are there to choose some of the items (zero items allowed) so that the total weight is exactlyk? Constraints:n ≤ 20,a[i] ≤ 10⁹.
How many possibilities? Each item is either taken or
not: 2²⁰ ≈ 1 million. Each possibility needs n steps to
sum: 21 million. It fits.
The code:
int banyak = 0;
for (int mask = 0; mask < (1 << n); mask++) {
long long jumlah = 0;
for (int i = 0; i < n; i++)
if (mask & (1 << i)) jumlah += a[i];
if (jumlah == k) banyak++;
}
cout << banyak << "\n";If the constraint is n ≤ 40, this approach dies — and
the solution is to split the data into two halves, each 2²⁰. That
technique is outside the scope of this book, but the way of thinking is
the same: count first, then decide.
Homework
Problem 1 — Pairs summing to k
Open
exercises/11/homework/01-pasangan.cpp.
Read n (≤ 2,000), k, then n
numbers. Print the number of index pairs (i, j) with
i < j and a[i] + a[j] == k.
Problem 2 — Pythagorean triples
Open
exercises/11/homework/02-tripel.cpp.
Read n (≤ 300). Print the number of triples
(a, b, c) with 1 ≤ a < b < c ≤ n and
a² + b² = c². First count the steps of three nested loops
for n = 300, write it in a comment, and only then write the
code.
Problem 3 — Subsets summing to k
Open
exercises/11/homework/03-himpunan.cpp.
Read n (≤ 20), k, then n numbers.
Print the number of ways to choose some of the items whose sum is
exactly k. The empty set counts when
k = 0.
Problem 4 — Best ordering
Open
exercises/11/homework/04-urutan.cpp.
Read n (≤ 8) and an n × n distance matrix.
Print the length of the shortest route that visits every city exactly
once, starting from city 0. Use next_permutation on cities
1..n-1.
Challenge — Partial scores
Open
exercises/11/homework/05-subsoal.cpp.
The problem: print the largest difference a[j] − a[i]
with i < j (a kind of "largest profit from buying
then selling"). Write two functions: an O(n²) version
that checks every pair, and an O(n) version that keeps the smallest
value so far. Compare the two with a random tester as in Chapter 10,
then measure their time for n = 50,000.
Most common mistakes
| What was written | Consequence |
|---|---|
j = 0 although pairs are unordered |
every pair is counted twice |
| forgetting to include the empty set | answer is one short when k = 0 |
mask < (1 << n) written as
mask <= (1 << n) |
one bogus possibility gets counted |
sum accumulator of type int |
overflows on large data |
forgetting sort before
next_permutation |
some orderings are never tried |
| brute force written without counting first | Time Limit Exceeded after the code is done |
OSN tips
Small constraints are an open invitation. The following numbers almost always mean "try every possibility", and the problem setter put them there precisely so that brute force fits:
| Constraint seen | What the problem setter means | Number of possibilities |
|---|---|---|
N ≤ 10 |
every ordering | 10! = 3,628,800 |
N ≤ 20 |
every subset | 2²⁰ ≈ 10⁶ |
N ≤ 500 |
every triple | ≈ 2 × 10⁷ |
N ≤ 5,000 |
every pair | ≈ 1.25 × 10⁷ |
If the constraints look "too small", do not be suspicious — that is exactly the answer.
Brute force is a legitimate answer, not an embarrassing one. In problems with subtasks, a brute force answer solves the small-constraint subtasks and earns real points. A contestant who spends all their time looking for the perfect answer and submits nothing gets zero.
Write the brute force first even if you know the fast approach. It becomes the reference for testing the fast answer on random input, as in the Testing against a reference section of Chapter 10. Many subtle mistakes in a fast answer can only be found this way.
Arrange the loops so nothing is counted twice. To
choose pairs or triples from distinct positions, start j
from i + 1 and k from j + 1. This
also avoids a division at the end and guarantees every combination is
counted exactly once.
Prune as early as possible. A check that can be done in an outer loop should not be postponed to an inner loop. Moving one condition from the innermost level to the second level often cuts the running time by up to ten times, without changing the answer at all.
Know the upper bound of the answer before choosing a
type. Brute force often sums a very large number of
possibilities. The number of triples for N = 500 alone is
already 20 million; if what is summed is values rather than counts,
long long is almost always required.
The form
for (int mask = 0; mask < (1 << n); mask++) is the
standard way to enumerate every subset. Bit i of
mask being set means element i is chosen,
checked with mask & (1 << i). For n
= 20 that is 1,048,576 subsets — one second is more than enough.
Summary
- Brute force means trying every possibility; it is useful as the full answer for small constraints, a partial answer for large constraints, and a reference for testing fast solutions.
- The order of work: decide what to try, count how many, compare with the constraints table in Chapter 10, and only then write the code.
- The four standard forms: every element O(n), every pair O(n²), every subset O(2ⁿ), every ordering O(n!).
- Subsets are written with a binary
mask;1 << nis 2ⁿ andmask & (1 << i)checks biti. - All orderings are produced by
next_permutation, and the data must be sorted first. - Pruning can remove one level of loops, but it cannot turn 2ⁿ into n². If the constraints are far out of reach, a different idea is needed — Chapter 12 or Chapter 13.
OSN-style exercises
The following three problems are the three most frequent forms of brute force: every triple, every subset, and every ordering. Pay attention to the constraints — each was chosen so that it fits exactly.
The skeletons for all three are in
exercises/11/osn/.
Problem 1 — Zero-Sum Triples
Given N integers. Count how many position triples
(i, j, k) with i < j < k satisfy
Aᵢ + A_j + A_k = 0.
Input format. The first line contains an integer
N. The second line contains N integers.
Output format. One line containing the number of triples.
Constraints.
- 3 ≤
N≤ 500 - −10⁹ ≤
Aᵢ≤ 10⁹
Sample input
6
-1 0 1 2 -1 -4
Sample output
3
Explanation. The triples that sum to zero are positions (1, 2, 3) holding −1, 0, 1; positions (2, 3, 5) holding 0, 1, −1; and positions (1, 4, 5) holding −1, 2, −1. What is counted is triples of positions, so two triples with the same values but different positions are counted twice.
Skeleton:
exercises/11/osn/01-tripel-nol.cpp
Problem 2 — Closest Subset
An organizer has N boxes of different weights and wants
to load some of them into a car whose capacity is approximately
S. They may choose any boxes, including choosing none at
all.
Find the total weight closest to S — it may be less or
more. If two choices are equally far from S, choose the
smaller total weight.
Input format. The first line contains two integers
N and S. The second line contains
N integers.
Output format. One line containing the total weight
closest to S.
Constraints.
- 1 ≤
N≤ 20 - 1 ≤
S≤ 10¹⁸ - 1 ≤
Aᵢ≤ 10⁹
Sample input
4 10
3 34 4 12
Sample output
12
Explanation. Possible totals include 0, 3, 4, 7, 12, 15, 16, and so on. The closest to 10 is 12, at distance 2; the choice 7 is at distance 3.
Skeleton:
exercises/11/osn/02-himpunan-terdekat.cpp
Problem 3 — Shortest Tour
A courier must depart from the first city, visit every city exactly once, and then return to the first city. The distances between cities are given as a table. Find the shortest possible total distance.
Input format. The first line contains an integer
N. Each of the next N lines contains
N integers; the j-th number on line
i is the distance from city i to city
j.
Output format. One line containing the shortest total distance.
Constraints.
- 2 ≤
N≤ 8 - 0 ≤ distance ≤ 1000, and the distance from a city to itself is 0.
Sample input
4
0 10 15 20
10 0 35 25
15 35 0 30
20 25 30 0
Sample output
80
Explanation. One shortest tour is city 1 → 2 → 4 → 3 → 1, with a total of 10 + 25 + 30 + 15 = 80.
Skeleton:
exercises/11/osn/03-tur-terpendek.cpp
Stuck or already done? Open the homework solutions page.