Problème de regroupement — Solo ou Binôme
Lors de la préparation de la première épreuve éliminatoire organisée par les Olympiades Marocaines d'Informatique, le comité donne le choix aux étudiants de travailler seuls ou en binôme avec un autre étudiant.
Le problème se réduit à savoir de combien de manières les étudiants peuvent rester seuls ou en binôme.
Ce problème est un classique de la programmation dynamique et correspond au nombre de couplages dans un graphe complet (connu sous le nom de "nombres de téléphone" ou "involution numbers").
Énoncé du problème
Entrée
- Un entier n représentant le nombre d'étudiants.
Sortie
- Le nombre de façons de regrouper les étudiants en solo ou en binômes.
3
4
- {1}, {2}, {3}
- {1, 2}, {3}
- {1, 3}, {2}
- {1}, {2, 3}
4
10
40
8166103632745757696
- 1 ≤ n ≤ 50
Solution 1 — Approche récursive (Diviser pour régner)
Le problème peut être résolu avec la récursivité en considérant les choix que nous avons pour le n-ième étudiant :
- Cas 1 : Le n-ième étudiant reste seul → on récure pour les (n-1) étudiants restants.
- Cas 2 : Le n-ième étudiant se met en binôme avec n'importe lequel des (n-1) élèves restants, puis on récure pour les (n-2) étudiants restants. Le nombre de façons de faire est (n-1) × Rec(n-2).
f(n) = f(n-1) + (n-1) × f(n-2)
Avec les cas de base :
f(1) = 1(un seul étudiant reste seul)f(2) = 2(deux étudiants peuvent rester seuls ou former un binôme)
si n ≤ 2 → retourner ndef CompterGroupes(n):
if n <= 2:
return n
else:
return CompterGroupes(n-1) + (n-1) * CompterGroupes(n-2)
n = int(input())
print(CompterGroupes(n))#include <iostream>
using namespace std;
unsigned long long CompterGroupes(int n)
{
if (n <= 2) return n;
else return CompterGroupes(n-1) + (n-1) * CompterGroupes(n-2);
}
int main()
{
int n;
cin >> n;
cout << CompterGroupes(n) << endl;
return 0;
}4
Complexité temporelle : exponentielle (similaire à Fibonacci mais avec multiplications).
Complexité spatiale : O(n) — profondeur de la pile d'appels.
Pour n=50, cette solution est beaucoup trop lente.
Solution 2 — Programmation Dynamique
Puisque la formule récursive ci-dessus (solution 1) a des sous-problèmes qui se chevauchent, nous pouvons la résoudre en utilisant la programmation dynamique.
dp[n+1] où :dp[i] = nombre de façons de regrouper i étudiants.- Initialisation :
dp[0] = 1(ensemble vide, une seule façon)dp[1] = 1(un seul étudiant reste seul)
- Récurrence :Pour i de 2 à n
dp[i] = dp[i-1] + (i-1) × dp[i-2]
Illustration des premières valeurs
| n | dp[n] | Explication |
|---|---|---|
| 0 | 1 | ensemble vide |
| 1 | 1 | {1} |
| 2 | 2 | {1,2} ou {1},{2} |
| 3 | 4 | exemple 1 |
| 4 | 10 | exemple 2 |
| 5 | 26 | = 10 + 4×4 |
def CompterGroupes(n):
# Tableau DP
dp = [0] * (n + 1)
for i in range(n + 1):
if i <= 2:
dp[i] = i
else:
dp[i] = dp[i-1] + (i-1) * dp[i-2]
return dp[n]
n = int(input())
print(CompterGroupes(n))#include <iostream>
using namespace std;
unsigned long long CompterGroupes(int n)
{
unsigned long long dp[n + 1];
for (int i = 0; i <= n; i++) {
if (i <= 2)
dp[i] = i;
else
dp[i] = dp[i - 1] + (i - 1) * dp[i - 2];
}
return dp[n];
}
int main()
{
int n;
cin >> n;
cout << CompterGroupes(n) << endl;
return 0;
}8166103632745757696
| Critère | Solution récursive naïve | Programmation dynamique |
|---|---|---|
| Temps | Exponentiel (∼ φⁿ) | O(n) — linéaire |
| Espace | O(n) — pile d'appels | O(n) — tableau 1D |
| Sous-problèmes recalculés | Oui — nombreux recalculs | Non — mémoïsation |
On peut réduire l'espace à O(1) en ne gardant que les deux dernières valeurs :
unsigned long long CompterGroupes(int n) {
if (n <= 2) return n;
unsigned long long prev2 = 1; // dp[1]
unsigned long long prev1 = 2; // dp[2]
unsigned long long curr;
for (int i = 3; i <= n; i++) {
curr = prev1 + (i - 1) * prev2;
prev2 = prev1;
prev1 = curr;
}
return curr;
}- Ce problème correspond au nombre de façons de partitionner un ensemble en paires et éléments seuls.
- La relation de récurrence est :
f(n) = f(n-1) + (n-1) × f(n-2). - Les cas de base sont
f(0)=1,f(1)=1,f(2)=2. - La solution récursive naïve a une complexité exponentielle.
- La programmation dynamique permet de résoudre le problème en temps linéaire O(n).
- Pour n=50, les résultats deviennent très grands (dépassent 64 bits pour n>50).
- Ce problème est connu sous le nom de "nombres de téléphone" (telephone numbers) ou "involution numbers".
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.