Les secrets de la célèbre prison Habs Qara à Meknès - Programmation compétitive

21 Apr 2022 21 Apr 2022 2259 vues ESSADDOUKI Mostafa 8 min de lecture

Problème d'allocation de livres — Recherche binaire

Ces dernières années, les chercheurs ont trouvé des livres manuscrits intéressants numérotés sur l'histoire du sultan du Maroc de 1672 à 1727, Moulay Ismail Ibn Sharif. Les chercheurs pensent que ces livres révéleront les secrets de la célèbre prison Habs Qara. Alors, ils ont décidé de lire attentivement ces livres.

Pour accélérer la lecture, les chercheurs ont décidé de séparer la tâche entre eux de manière égale, de sorte que chaque chercheur est chargé de lire des livres consécutifs de telle sorte que le nombre maximum de pages soit minimisé.

Lorsqu'un chercheur commence la lecture d'un livre, il doit terminer la lecture, donc chaque livre ne peut être lu que par un seul chercheur.

Définition — Problème d'allocation Étant donné un ensemble de n livres (chacun avec un nombre de pages) et m chercheurs, on veut répartir les livres entre les chercheurs de manière consécutive (les livres attribués à un chercheur forment un bloc continu) de façon à minimiser le nombre maximum de pages qu'un chercheur doit lire.

Ce problème est un classique de la recherche binaire sur la réponse.

Énoncé du problème

Entrée
  • La première ligne contient le nombre de livres (n) et le nombre de chercheurs (m).
  • La ligne suivante contient le nombre de pages dans chaque livre.
Sortie
  • Le nombre maximum de pages pouvant être affectées à chaque chercheur (minimisé).
  • Si la répartition est impossible (plus de chercheurs que de livres), retourner -1.
Exemple 1
Entrée
2 2
15 40
Sortie
40
Explication : 2 chercheurs pour 2 livres → chaque chercheur prend un livre. Le maximum est max(15, 40) = 40.
Exemple 2
Entrée
4 5
12 10 16 30
Sortie
-1
Explication : 5 chercheurs pour 4 livres → impossible (chaque livre ne peut être lu que par un seul chercheur).
Exemple 3
Entrée
5 3
10 20 30 25 40
Sortie
55
Explication : Une répartition possible : [10,20,30] pour le chercheur 1 (60 pages), [25,40] pour le chercheur 2 (65 pages) → max = 65. Mais une meilleure répartition existe : [10,20] (30), [30,25] (55), [40] (40) → max = 55.
Contraintes
  • 1 ≤ n, m ≤ 10⁵
  • 1 ≤ pages[i] ≤ 10⁴

Solution — Recherche binaire sur la réponse

L'idée est d'utiliser la recherche binaire pour trouver la valeur minimale du maximum de pages par chercheur.

Principe de la recherche binaire

Nous fixons une valeur candidate mid (le nombre de pages maximum que l'on veut tester). On vérifie si cette valeur est faisable, c'est-à-dire s'il est possible de répartir tous les livres entre les chercheurs avec cette limite.

  • Borne inférieure : 0 (ou la page maximale d'un seul livre)
  • Borne supérieure : somme de toutes les pages

Si mid est faisable, alors on cherche une valeur plus petite dans la moitié inférieure. Sinon, on cherche une valeur plus grande dans la moitié supérieure.

Vérification de faisabilité

Pour vérifier si une valeur min_actuel est faisable :

  • On parcourt les livres dans l'ordre (ils doivent être attribués de façon consécutive).
  • On alloue les livres au chercheur courant tant que le total ne dépasse pas min_actuel.
  • Si le total dépasse, on passe au chercheur suivant.
  • Si le nombre de chercheurs nécessaires dépasse m, la valeur n'est pas faisable.
  • Si un livre seul a plus de pages que min_actuel, c'est immédiatement impossible.
Illustration — Exemple 3 : pages = [10,20,30,25,40], m = 3

Testons la valeur candidate 55 :

  • Chercheur 1 : 10 + 20 = 30 (≤ 55), ajouter 30 → 60 > 55 donc on arrête
  • Chercheur 2 : 30 (déjà compté) + 25 = 55 (≤ 55), ajouter 40 → 95 > 55 donc on arrête
  • Chercheur 3 : 40 (≤ 55)
  • Total chercheurs utilisés : 3 ≤ 3 → faisable ✓

Testons la valeur candidate 54 :

  • Chercheur 1 : 10 + 20 = 30 (≤ 54), ajouter 30 → 60 > 54 donc on arrête
  • Chercheur 2 : 30 + 25 = 55 > 54 → on arrête immédiatement, donc chercheur 2 prend juste 30? Non, re-comptons:
    • Chercheur 1: 10+20=30, 30+30=60 > 54 → chercheur 1 lit [10,20] (30 pages)
    • Chercheur 2: commence à 30, 30+25=55 > 54 → chercheur 2 lit [30] (30 pages)
    • Chercheur 3: commence à 25, 25+40=65 > 54 → chercheur 3 lit [25] (25 pages)
    • Il reste le livre 40 non attribué → il faut un 4ème chercheur → 4 > 3 → non faisable ✗
import sys

def faisable(pages, n, m, min_actuel):
    """
    Vérifie si on peut répartir les livres avec un maximum de min_actuel pages par chercheur
    """
    besoin_chercheurs = 1
    somme_actuelle = 0

    for i in range(n):
        # Si un livre dépasse déjà la limite, impossible
        if pages[i] > min_actuel:
            return False

        # Si l'ajout du livre dépasse la limite, on passe au chercheur suivant
        if somme_actuelle + pages[i] > min_actuel:
            besoin_chercheurs += 1
            somme_actuelle = pages[i]

            # Si on a besoin de plus de chercheurs que disponible
            if besoin_chercheurs > m:
                return False
        else:
            somme_actuelle += pages[i]
    
    return True


def solution(pages, n, m):
    # Retourner -1 si le nombre de livres est inférieur au nombre de chercheurs
    if n < m:
        return -1
    
    # Trier les pages (optionnel, mais facilite la vérification)
    pages.sort()
    
    # Calculer la somme des pages
    somme = sum(pages)

    debut = 0
    fin = somme
    maxpages = float('inf')

    # Recherche binaire
    while debut <= fin:
        milieu = (debut + fin) // 2
        if faisable(pages, n, m, milieu):
            maxpages = milieu
            fin = milieu - 1
        else:
            debut = milieu + 1

    return maxpages


# Lecture de l'entrée
n, m = map(int, sys.stdin.readline().strip().split())
pages = list(map(int, sys.stdin.readline().strip().split()))

print(solution(pages, n, m))
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <climits>

using namespace std;

bool faisable(const vector<int> &pages, int n, int m, int min_actuel) {
    int besoin_chercheurs = 1;
    int somme_actuelle = 0;

    for (int i = 0; i < n; i++) {
        // Si un livre dépasse déjà la limite, impossible
        if (pages[i] > min_actuel)
            return false;

        // Si l'ajout du livre dépasse la limite, on passe au chercheur suivant
        if (somme_actuelle + pages[i] > min_actuel) {
            besoin_chercheurs++;
            somme_actuelle = pages[i];

            // Si on a besoin de plus de chercheurs que disponible
            if (besoin_chercheurs > m)
                return false;
        } else {
            somme_actuelle += pages[i];
        }
    }
    return true;
}

int solution(vector<int> pages, int n, int m) {
    // Retourner -1 si le nombre de livres est inférieur au nombre de chercheurs
    if (n < m)
        return -1;
    
    // Trier les pages (optionnel)
    sort(pages.begin(), pages.end());
    
    // Calculer la somme des pages
    long long somme = accumulate(pages.begin(), pages.end(), 0);

    int debut = 0;
    int fin = somme;
    int maxpages = INT_MAX;

    // Recherche binaire
    while (debut <= fin) {
        int milieu = (debut + fin) / 2;
        if (faisable(pages, n, m, milieu)) {
            maxpages = milieu;
            fin = milieu - 1;
        } else {
            debut = milieu + 1;
        }
    }
    return maxpages;
}

int main() {
    int n, m, val;
    cin >> n >> m;
    vector<int> pages;
    for (int i = 0; i < n; i++) {
        cin >> val;
        pages.push_back(val);
    }

    cout << solution(pages, n, m) << endl;
    
    return 0;
}
Sortie (Exemple 3)
55
Complexité — Solution par recherche binaire
CritèreValeur
Complexité temporelleO(n × log(S)) où S = somme des pages
Complexité spatialeO(1) (hors stockage du tableau)

La recherche binaire effectue environ log₂(S) itérations, et chaque vérification de faisabilité parcourt les n livres en O(n).

Optimisation de la borne inférieure

On peut améliorer la recherche binaire en initialisant la borne inférieure non pas à 0 mais au maximum des pages d'un seul livre, car toute solution valide doit au moins pouvoir contenir le plus gros livre.

int debut = *max_element(pages.begin(), pages.end());  // au lieu de 0

Cela réduit l'espace de recherche et peut accélérer l'algorithme.

Points clés à retenir
  • Le problème d'allocation de livres consiste à répartir des livres consécutifs entre chercheurs en minimisant le maximum de pages par chercheur.
  • On utilise une recherche binaire sur la valeur du maximum à minimiser.
  • La vérification de faisabilité se fait en O(n) en simulant l'allocation séquentielle.
  • Si le nombre de chercheurs est supérieur au nombre de livres, la réponse est -1.
  • Complexité totale : O(n × log(somme)).
  • La borne inférieure peut être optimisée en prenant le maximum des pages d'un seul livre.
  • Ce problème est une variante du problème de partitionnement avec contrainte de consécutivité.
Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.