De ce contează?
Imaginează-ți un oraș în care toate străzile sunt cu sens unic. Te întrebi: pot ajunge cu mașina din orice intersecție în orice altă intersecție și apoi să mă și întorc, fără să încalc niciun sens? Dacă răspunsul e „da” pentru fiecare pereche de intersecții, atunci harta orașului e tare conexă. Dacă există fie și o singură intersecție din care, odată ajuns, nu mai poți ieși, magia se rupe.
Intuiția
Într-un graf neorientat ne întrebam doar dacă totul e „legat” — conexitate.
La un graf orientat, muchiile au sens, deci a ajunge din u în v NU mai
garantează că ajungi și din v în u. Un graf orientat e tare conex dacă
pentru orice pereche de noduri (u, v) există drum u → v ȘI drum v → u.
Pe scurt: din oriunde ajungi oriunde, respectând sensul.
Conexitatea „simplă” ignoră sensul săgeților — e o întrebare despre graful neorientat de dedesubt. Tare conex ia sensul în serios: dus-întors garantat între oricare două noduri. Iar un nod singur e trivial tare conex cu el însuși, deci o componentă tare conexă poate avea și un singur nod.
Vezi cum funcționează
Pornește animația și urmărește cum nodurile se grupează în componente tare conexe (CTC): insule de noduri din care toate ajung reciproc unele la altele. Graful întreg e tare conex exact când există o singură astfel de insulă. Animația rulează algoritmul lui Kosaraju — îl vei diseca în capitolul de parcurgeri; aici concentrează-te pe rezultat: fiecare culoare = o insulă.
Folosește ← și → ca să pășești prin algoritm, sau Redă pentru animație. Observă cum nodurile dintr-un ciclu primesc aceeași culoare — sunt în aceeași CTC.
Cum verifici „tare conex” din instinct? Pentru fiecare pereche (u, v) pornești
un drum din u și verifici dacă atingi v, apoi un drum din v și verifici
dacă atingi u. Dacă merge pentru toate perechile, graful e tare conex.
Sunt n² perechi, iar fiecare verificare (un BFS/DFS) costă O(n+m). Total:
O(n²·(n+m)). La n = 1000 și m = 5000 ajungi la miliarde de operații — TLE
sigur la o limită de 1 secundă. Faci de mii de ori aceeași muncă.
„Ajung din u în v ȘI din v în u” e o relație de echivalență: e reflexivă, simetrică și tranzitivă. Deci nodurile se împart natural în clase — componentele tare conexe. Nu trebuie să testez perechi: trebuie doar să număr câte clase sunt.
Împart graful în CTC printr-un algoritm liniar (Kosaraju sau Tarjan, două DFS-uri). Graful e tare conex dacă și numai dacă are exact o singură CTC. Pentru un simplu „da/nu” îmi ajunge truc-ul cu graful transpus, fără să construiesc toate CTC.
Algoritmul pas cu pas
Fie graful orientat cu 4 noduri și muchiile: 1→2, 2→3, 3→1, 1→4.
Nodurile 1, 2, 3 formează un circuit; nodul 4 primește o muchie, dar din el
nu iese nimic.
Pasul 1 — circuitul 1→2→3→1. Verifică perechile una câte una: 1→2 direct
și înapoi 2→3→1; 2→3 direct și înapoi 3→1→2; 1→2→3 dus și 3→1 întors.
Între oricare două dintre {1, 2, 3} există drum dus-întors: ele sunt în
aceeași CTC.
1 | 2 | 3 | 4 |
0 | 1 | 2 | 3 |
CTC=A | CTC=A | CTC=A | ? |
Pasul 2 — nodul 4. Ajung din 1 în 4 (muchia 1→4), dar din 4 nu
iese nicio muchie, deci NU există drum 4 → 1. Drumul trebuie să existe în
ambele sensuri; aici lipsește întoarcerea. Așadar 4 NU intră în CTC-ul lui
{1, 2, 3} — formează singur componenta B (o CTC perfect validă de un singur
nod).
1 | 2 | 3 | 4 |
0 | 1 | 2 | 3 |
CTC=A | CTC=A | CTC=A | CTC=B |
Concluzie. Avem 2 componente tare conexe, nu una singură. Prin urmare
graful NU e tare conex — exact pentru că 4 e o „fundătură”. Dacă am adăuga
muchia 4→1, atunci 4 ar putea reveni la circuit și totul ar deveni o singură
CTC.
Graful orientat e tare conex dacă și numai dacă numărul de componente tare
conexe este exact 1. Un singur nod din care nu poți ieși (sau în care nu poți
intra) ridică numărul de CTC peste 1 și strică tare-conexitatea.
Implementare C++
Pentru un simplu test „da/nu” nu construim toate CTC. Folosim observația: graful
e tare conex dacă, pornind dintr-un nod fixat (nodul 0), ajung peste tot în
graful normal ȘI ajung peste tot în graful transpus (cu toate muchiile
întoarse). Primul DFS verifică 0 → orice; al doilea verifică orice → 0.
Împreună: între orice u și v am drum u → 0 → v și retur.
#include <iostream>
#include <vector>
using namespace std;
int n; // numar de noduri (0..n-1)
vector<int> graf[100], transp[100]; // graful normal si cel transpus
bool vizitat[100];
// DFS clasic care marcheaza toate nodurile atinse din 'nod'
void dfs(vector<int> g[], int nod) {
vizitat[nod] = true;
for (int vecin : g[nod]) {
if (!vizitat[vecin]) {
dfs(g, vecin);
}
}
}
// true daca toate nodurile sunt atinse dintr-un DFS pornit din 0
bool toateAtinse(vector<int> g[]) {
for (int i = 0; i < n; i++) {
vizitat[i] = false;
}
dfs(g, 0);
for (int i = 0; i < n; i++) {
if (!vizitat[i]) {
return false; // nodul i nu a fost atins
}
}
return true;
}
int main() {
int m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b; // muchie orientata a -> b
graf[a].push_back(b);
transp[b].push_back(a); // in transpus, muchia merge invers
}
// tare conex daca din 0 ajung peste tot SI in transpus la fel
bool tareConex = toateAtinse(graf) && toateAtinse(transp);
cout << (tareConex ? "tare conex" : "NU e tare conex") << "\n";
// exemplul lectiei renumerotat 0..3 (muchii 0->1, 1->2, 2->0, 0->3):
// primul DFS atinge tot, dar in transpus DFS din 0 NU atinge nodul 3
// (adica in graful original 3 nu ajunge la 0) => NU e tare conex
return 0;
}Complexitate
| Caz | Timp | Spațiu |
|---|---|---|
| Verificare tare-conexitate (2 DFS) | O(n+m) | O(n+m) |
Fiecare DFS vizitează fiecare nod o dată și parcurge fiecare muchie o dată, deci
O(n+m). Două DFS-uri rămân O(n+m) — un salt uriaș față de O(n²·(n+m)) al
abordării naive.
Trei capcane clasice:
- Aplici conexitatea neorientată. Tratezi muchiile ca bidirecționale și
ignori sensul. Atunci
1→4ar părea că leagă4de rest — fals: din4nu există drum de întoarcere. La grafuri orientate sensul e totul. - Uiți graful transpus. Verifici doar că din
0ajungi peste tot și declari graful tare conex. Dar asta arată doar0 → orice, nu șiorice → 0. Fără al doilea DFS (pe transpus) ratezi nodurile din care nu te poți întoarce. - Crezi că o muchie între două CTC-uri le unește. Muchia
1→4există deja în exemplu, dar nu-l aduce pe4în componentaA: o săgeată dă doar drumul dus. Cele două componente fuzionează numai dacă apare și un drum de întoarcere — de pildă adăugarea muchiei4→1transformă tot graful într-o singură CTC.