Installation des réservoirs et robinets dans un quartier - Programmation compétitive

20 Apr 2022 20 Apr 2022 2151 vues ESSADDOUKI Mostafa 11 min de lecture

Problème de réseau d'eau — Réservoirs et robinets

Ces dernières années, nous remarquons qu'il n'y a pas eu assez de pluie, ce qui entraîne un faible volume d'eau dans tous les barrages du Maroc. Pour cette raison, il peut y avoir des problèmes pour desservir les maisons en eau cet été.

Ainsi, les autorités des villes qui auront ce problème, par exemple Zagora, ont décidé d'installer des réservoirs dans certaines maisons qui fourniront de l'eau potable à d'autres maisons via des tuyaux, de sorte que chaque maison ait au plus un tuyau d'arrivée et au plus un tuyau de sortie. Une question se pose alors : où devrions-nous installer les réservoirs et les robinets ?

Les réservoirs sont simplement installés dans les maisons ayant un tuyau de sortie mais pas de tuyau d'arrivée, et dans les maisons n'ayant qu'un tuyau d'arrivée et pas de tuyau de sortie, il y a un robinet. Le comité chargé de fournir l'eau potable a décidé de créer un réseau efficace.

Définition — Problème de réseau d'eau Étant donné un ensemble de n maisons et ptuyaux orientés (chaque tuyau va d'une maison source à une maison destination avec un diamètre donné), on cherche à identifier :
  • Réservoirs : maisons qui ont un tuyau de sortie mais pas de tuyau d'entrée
  • Robinet : maisons qui ont un tuyau d'entrée mais pas de tuyau de sortie
Pour chaque composante connectée (chaîne de maisons), on détermine le réservoir (début), le robinet (fin) et le diamètre minimum du tuyau dans cette chaîne.

Énoncé du problème

Entrée
  • La première ligne contient deux nombres n (nombre de maisons) et p (nombre de tuyaux).
  • Les p lignes suivantes contiennent les connexions de tuyaux entre les maisons, chaque connexion contient trois valeurs : f_i, t_i, d_i désignant le tuyau de diamètre d_i de la maison f_i à la maison t_i.
Sortie
  • La première ligne contient le nombre t de paires (réservoir, robinet) installées.
  • Les t lignes suivantes contiennent trois nombres entiers : le numéro de la maison du réservoir, le numéro de la maison du robinet et le diamètre minimum du tuyau entre eux.
Exemple 1
Entrée
8 5
5 8 35
2 1 30
4 3 20
1 5 20
6 7 25
Sortie
3
2 8 20
4 3 20
6 7 25
Explication : Trois composantes connectées : 2→1→5→8 (diamètre min 20), 4→3 (diamètre min 20), 6→7 (diamètre min 25).
Exemple 2
Entrée
9 6
7 4 98
5 9 72
4 6 10
2 8 22
9 7 17
3 1 66
Sortie
3
2 8 22
3 1 66
5 6 10
Explication : Trois composantes : 2→8, 3→1, 5→9→7→4→6 (diamètre min 10).
Contraintes
  • 1 ≤ n ≤ 10³
  • 0 ≤ p ≤ 10³
  • 1 ≤ f_i, t_i ≤ n
  • 1 ≤ d_i ≤ 10³
  • Chaque maison a au plus un tuyau d'entrée et au plus un tuyau de sortie.

Illustration du problème

Réseau de tuyaux

Figure : Représentation graphique des connexions de l'exemple 1

La figure ci-dessus représente les maisons connectées fournies dans l'entrée sous forme de graphe. Découvrons où placer les réservoirs et les robinets dans cette configuration.

  • En 2→8, 2 n'a qu'un tuyau de sortie, il aura donc un réservoir installé. 8 n'a qu'un tuyau d'arrivée, il y aura donc un robinet installé. Puisqu'il n'y a pas d'autres maisons connectées, le diamètre minimum sera de 20.
  • De même, en 4→3, 4 a un tuyau de départ donc un réservoir et 3 a un tuyau d'arrivée donc un robinet. Ici, le diamètre minimum est de 20.
  • Enfin, en 6→7, 6 a un tuyau de départ donc un réservoir et 7 a un tuyau d'arrivée donc un robinet. Ici le diamètre minimum est de 25.

Approche de résolution

Observations clés
  • Réservoir : maison avec un tuyau de sortie mais pas de tuyau d'entrée.
  • Robinet : maison avec un tuyau d'entrée mais pas de tuyau de sortie.
  • Le réseau forme des chaînes (pas de cycles) car chaque maison a au plus un entrant et au plus un sortant.
  • Chaque chaîne commence par un réservoir et se termine par un robinet.

Effectuez un parcours DFS ou BFS à partir des maisons appropriées pour trouver toutes les différentes composantes connectées. Le nombre de composants connectés différents est notre réponse t.

Les t lignes suivantes de la sortie sont le début de la composante connectée (réservoir), la fin de la composante connectée (robinet) et le diamètre minimum du début à la fin dans chaque ligne.

Étant donné que les réservoirs ne peuvent être installés que sur les maisons ayant un tuyau sortant et aucun tuyau entrant, ce sont donc des maisons appropriées pour démarrer DFS (ou BFS) à partir de ces maisons non visitées.

#include <iostream>
#include <cstring>
#include <vector>
#include <stack>
#include <set>
#include <algorithm>
#include <climits>

using namespace std;

// Classe pour représenter le graphe
class GraphAdjList {
    private:
        vector<vector<int>> voisins;
        vector<vector<int>> diametres;
        int nbMaisons;
        
    public:
        GraphAdjList(const vector<vector<int>> &tuyaux, int nb_maisons) {
            nbMaisons = nb_maisons;
            voisins.resize(nb_maisons);
            diametres.resize(nb_maisons);
            
            // tuyaux[i][0] => f_i ; tuyaux[i][1] => t_i ; tuyaux[i][2] => d_i
            for (int i = 0; i < tuyaux.size(); i++) {
                // ajouter t_i dans la liste d'adjacence de f_i
                voisins[tuyaux[i][0]].push_back(tuyaux[i][1]);
                // le diamètre entre f_i et t_i est d_i
                diametres[tuyaux[i][0]].push_back(tuyaux[i][2]);
            }
        }

        const vector<vector<int>> & getVoisins() { return voisins; }
        const vector<vector<int>> & getDiametres() { return diametres; }
        int getNbMaisons() { return nbMaisons; }
};

// Parcours en largeur pour trouver toutes les maisons connectées à partir de 'depart'
vector<int> parcours_largeur(GraphAdjList &reseau, int depart) {
    vector<int> connected;
    bool visited[reseau.getNbMaisons()];
    memset(visited, false, sizeof(visited));
    
    stack<int> pile;
    pile.push(depart);
    
    while (!pile.empty()) {
        int current = pile.top();
        pile.pop();
        
        if (!visited[current]) {
            connected.push_back(current);
            visited[current] = true;

            for (int v : reseau.getVoisins()[current]) {
                pile.push(v);
            }
        }
    }
    return connected;
}

void solution(vector<vector<int>> tuyaux, int n) {
    // Créer le graphe (les maisons sont numérotées de 1 à n, on utilise n+1 pour simplifier)
    GraphAdjList reseau(tuyaux, n + 1);

    // Stocker tous les noeuds qui ont des arêtes entrantes (robinets potentiels)
    set<int> robinets;
    for (auto &tuyau : tuyaux) {
        robinets.insert(tuyau[1]);
    }

    // Stocker les réservoirs (noeuds avec arêtes sortantes mais sans arêtes entrantes)
    set<int> reservoires;
    for (auto &tuyau : tuyaux) {
        if (robinets.count(tuyau[0]) == 0) {
            reservoires.insert(tuyau[0]);
        }
    }

    // Afficher le nombre de réservoirs
    cout << reservoires.size() << endl;

    // Pour chaque réservoir, trouver la chaîne connectée
    for (int depart : reservoires) {
        vector<int> connected = parcours_largeur(reseau, depart);
        
        // Trouver le diamètre minimum dans cette chaîne
        int mini = INT_MAX;
        for (int i = 0; i < connected.size() - 1; i++) {
            vector<int> w = reseau.getDiametres()[connected[i]];
            int min_diam = *min_element(w.begin(), w.end());
            mini = min(min_diam, mini);
        }
        
        // Afficher réservoir, robinet (dernier élément), diamètre minimum
        cout << connected[0] << " " << connected[connected.size() - 1] << " " << mini << endl;
    }
}

int main() {
    int n, p, val;
    cin >> n >> p;
    
    vector<vector<int>> tuyaux(p);
    for (int i = 0; i < p; i++) {
        for (int j = 0; j < 3; j++) {
            cin >> val;
            tuyaux[i].push_back(val);
        }
    }

    solution(tuyaux, n);
    
    return 0;
}
import sys

# Construire la matrice d'adjacence pour le graphe
class GraphAdjList:
    def __init__(self, nb_maisons, liaisons):
        self.voisins = [[] for _ in range(nb_maisons)]
        self.diametres = [[] for _ in range(nb_maisons)]
        
        # pour chaque f_i, t_i, d_i
        for f, t, d in liaisons:
            # ajouter t dans la liste d'adjacence de f
            self.voisins[f].append(t)
            # le diamètre entre f et t est d
            self.diametres[f].append(d)

# Parcours en largeur pour trouver toutes les maisons connectées à partir de 'depart'
def parcours_largeur(reseau, depart):
    visited = [False] * len(reseau.voisins)
    pile = [depart]
    connected = []
    
    while pile:
        current = pile.pop()
        if not visited[current]:
            connected.append(current)
            visited[current] = True
            for v in reseau.voisins[current]:
                pile.append(v)

    return connected

# Résoudre le problème
def solution(tuyaux, n):
    # Créer le graphe (les maisons sont numérotées de 1 à n, on utilise n+1 pour simplifier)
    reseau = GraphAdjList(n + 1, tuyaux)
    
    # Stocker tous les noeuds qui ont des arêtes entrantes (robinets potentiels)
    robinets = set([tuyau[1] for tuyau in tuyaux])

    # Stocker les réservoirs (noeuds avec arêtes sortantes mais sans arêtes entrantes)
    reservoires = set()
    for elem in tuyaux:
        if elem[0] not in robinets:
            reservoires.add(elem[0])
    
    # Afficher le nombre de réservoirs
    print(len(reservoires))

    # Pour chaque réservoir, trouver la chaîne connectée
    for reserv in reservoires:
        connected_maisons = parcours_largeur(reseau, reserv)
        
        # Trouver le diamètre minimum dans cette chaîne
        mini = float('inf')
        for i in range(len(connected_maisons) - 1):
            w = reseau.diametres[connected_maisons[i]]
            min_diam = min(w)
            mini = min(min_diam, mini)
        
        # Afficher réservoir, robinet (dernier élément), diamètre minimum
        print(f"{connected_maisons[0]} {connected_maisons[-1]} {mini}")


# Lecture de l'entrée
n, p = map(int, sys.stdin.readline().strip().split())
tuyaux = []
for _ in range(p):
    tuyaux.append(list(map(int, sys.stdin.readline().strip().split())))

solution(tuyaux, n)
Sortie (Exemple 1)
3
2 8 20
4 3 20
6 7 25
Complexité de la solution
CritèreValeur
Construction du grapheO(p)
Identification réservoirs/robinetsO(p)
Parcours BFS/DFSO(n + p)
TotalO(n + p)
Points importants à noter
  • Le graphe est acyclique grâce à la contrainte "chaque maison a au plus un tuyau d'entrée et au plus un tuyau de sortie".
  • Chaque composante connectée est une chaîne (pas de branchements).
  • Le diamètre minimum dans la chaîne est le minimum des diamètres de tous les tuyaux qui la composent.
  • Si une maison n'apparaît ni comme source ni comme destination dans les tuyaux, elle est isolée et n'est pas comptée.
  • On peut utiliser un simple parcours itératif au lieu de BFS/DFS car il n'y a pas de cycles.
Points clés à retenir
  • Le problème se modélise comme un graphe orienté sans cycles où chaque nœud a un degré entrant ≤ 1 et un degré sortant ≤ 1.
  • Les réservoirs sont les nœuds avec degré entrant = 0 et degré sortant = 1.
  • Les robinets sont les nœuds avec degré entrant = 1 et degré sortant = 0.
  • Chaque composante connectée est une chaîne linéaire allant d'un réservoir à un robinet.
  • La solution consiste à identifier toutes les chaînes et à trouver le diamètre minimum dans chacune.
  • Complexité linéaire O(n + p) — efficace pour les contraintes données.
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.