This page briefly introduces enumeration algorithms.
Introduction
Enumeration (English: Enumerate) is a problem-solving strategy that guesses the answer based on existing knowledge.
The idea of enumeration is to keep making guesses, trying the possibilities in a set one by one, and then checking whether the conditions of the problem hold.
Key points
Specify the solution space
Build a concise mathematical model.
When enumerating, think carefully: what are the possible cases? Which elements should be enumerated?
Reduce the enumeration space
What is the enumeration range? Does every piece of content need to be enumerated?
When solving a problem by enumeration, be sure to think through these two points; otherwise, unnecessary time overhead will result.
Choose an appropriate enumeration order
Determine this according to the problem. For example, if the sample problem asks for the largest prime satisfying the conditions, then enumerating from large to small is naturally more appropriate.
Examples
The following is an example of solving a problem with enumeration and optimizing the enumeration range.
Example
Given an array whose elements are all distinct and none equal to , find the number of pairs of numbers in the array whose sum is .
Solution
The code for enumerating two numbers is easy to write.
C++
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
if (a[i] + a[j] == 0) ++ans;
Python
for i in range(n):
for j in range(n):
if a[i] + a[j] == 0:
ans += 1
Java
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
if (a[i] + a[j] == 0) ++ans;
Let's see how the enumeration range can be optimized. Since the problem does not require the pairs to be ordered, the answer is twice the answer for the ordered case (consider that if (a, b) is an answer, then (b, a) is also an answer). In this case, we only need to count the answers after imposing an order, and multiply by at the end.
We may require the first number to appear later. The code is as follows:
C++
for (int i = 0; i < n; ++i)
for (int j = 0; j < i; ++j)
if (a[i] + a[j] == 0) ++ans;
ans *= 2;
Python
for i in range(n):
for j in range(i):
if a[i] + a[j] == 0:
ans += 1
ans *= 2
Java
for (int i = 0; i < n; ++i)
for (int j = 0; j < i; ++j)
if (a[i] + a[j] == 0) ++ans;
ans *= 2;
It is not difficult to see that the enumeration range of has already been reduced here, reducing the time overhead of this code.
We can optimize further on this basis.
Do both numbers necessarily have to be enumerated? After enumerating one number, the conditions of the problem determine the other elements (the other number). If we can find a way to directly determine whether the required number exists, we can eliminate the time spent enumerating the latter number. More advancedly, when the data range permits, we can use a bucket1 to record the numbers traversed.
C++
#include <cstring>
constexpr int MAXN = 100000; // Here MAXN is the bound on elements in the array
int solve(int n, int a[]) {
bool met[MAXN * 2 + 1]; // Create a bucket that can hold [-MAXN, MAXN]
memset(met, 0, sizeof(met));
int ans = 0;
for (int i = 0; i < n; ++i) {
if (met[MAXN - a[i]]) ++ans; // If the desired element is in the bucket, increment the answer by one
met[MAXN + a[i]] = true; // In any case, put the current element into the bucket
}
return ans * 2;
}
Python
met = [False] * (MAXN * 2 + 1)
for i in range(n):
if met[MAXN - a[i]]:
ans += 1
met[a[i] + MAXN] = True
ans *= 2
Java
boolean[] met = new boolean[MAXN * 2 + 1];
for (int i = 0; i < n; ++i) {
if (met[MAXN - a[i]]) ++ans;
met[MAXN + a[i]] = true;
}
ans *= 2;
Complexity analysis
- Time complexity analysis: The problem can be solved after traversing the array once; when is sufficiently large, the time complexity is .
- Space complexity analysis: .