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.
- 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
É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.
8 5 5 8 35 2 1 30 4 3 20 1 5 20 6 7 25
3 2 8 20 4 3 20 6 7 25
9 6 7 4 98 5 9 72 4 6 10 2 8 22 9 7 17 3 1 66
3 2 8 22 3 1 66 5 6 10
- 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

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
- 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)3 2 8 20 4 3 20 6 7 25
| Critère | Valeur |
|---|---|
| Construction du graphe | O(p) |
| Identification réservoirs/robinets | O(p) |
| Parcours BFS/DFS | O(n + p) |
| Total | O(n + p) |
- 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.
- 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.
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.