Pokojne sedíte vo svojej kancelárii a vychutnávate si kávu. Ste radi, že vás práve povýšili na CEO celoštátnej firmy „Kolosálne Spojenie Psikov“ a užívate si výhľad zo svojho okna. Spomínate si, ako ste začínali pracovať pre túto firmu, ktorá rozdeľuje elektrinu do všetkých miest nášho milovaného štátu. Pamätáte si výstavbu masívnej elektrárne a pokladanie káblov medzi našimi malebnými mestami.
Zrazu začujete krik a do vašej kancelárie vbehne vaša sekretárka Miška s desivou správou: gigantická elektráreň má problémy a jej produkcia elektriny kolíše. To môže viesť k preťaženiu siete a následným explóziám rozvodných staníc. Vypnúť elektráreň nie je možné, pretože by to zruinovalo vašu firmu. Našťastie vás napadne riešenie:
Každé mesto má rozvodnú stanicu, ktorú viete na diaľku vypnúť. Tým odpojíte mesto od siete, čím riskujete kritiku. Aby ste predišli kolapsu, potrebujete vypínať mestá v takom poradí, že ak odpojíte od siete prvých $j$ miest v poradí, všetky zvyšné mesta zostanú spojené s elektrárňou.
Elektrická sieť pozostáva z $n$ miest, očíslovaných od $0$ po $n-1$, $m$ káblov a práve jednej elektrárne, ktorá sa nachádza v meste $k$. Elektrina prúdi od elektrárne do všetkých miest, do ktorých sa z nej dá dostať káblom (káble vedia prenášať energiu obojsmerne).
Keď vypneme mesto od elektriny, vypneme všetky káble ktoré cez neho vedú, a teda cez neho nesmie ani prúdiť elektrina do iných miest.
Nájdite postupnosť v ktorej vieme odpájať mestá od elektriny, tak aby pre každé $1 \leq j \leq n$ platilo, že ak od elektriny odpojíme prvých $j$ miest, neovplyvníme tak elektrinu do zvyšných $n-j$ miest.
Všimnite si, že ak od elektriny odpojíte mesto s elektrárňou, do ďalších miest už elektrina ísť nevie (nemá odkiaľ).
Prvý riadok obsahuje čísla $1 \leq n \leq 10^5$ (počet miest), $0\leq m \leq \min(2\cdot 10^5, \frac{n(n-1)}{2})$ (počet káblov) a $0\leq k\leq n - 1$ (číslo mesta s elektrárňou), oddelené medzerou.
Nasleduje $m$ riadkov s dvojicami čísel $0\leq a_i\neq b_i\leq n-1$ oddelených medzerou, označujúcich, že medzi mestami $a_i$ a $b_i$ sa nachádza kábel.
Je garantované, že v zadanej elektrickej sieti má každé mesto prístup k elektrine.
Vypíšte jeden riadok v ktorom sa nachádza $n$ medzerami oddelených čísel: postupnosť v ktorom vieme odpájať mestá od elektriny.
V prípade, že existuje viac postupností, vypíšte ľubovoľnú.
V prípade, že používate python a rekurziu, nezabudnite, že
prednadstavený limit rekurzie v pythone je $1000$, a pre väčšie hĺbky ju treba
manuálne nastaviť v programe cez sys.setrecursionlimit.
Existujú štyri testovacie sady, každá za dva body, v ktorých platia nasledovné obmedzenia:
| Sada | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| $1 \leq N \leq$ | $10$ | $10^5$ | $200$ | $10^5$ |
| $1 \leq M \leq$ | $45$ | $10^5$ | $1\,000$ | $2\cdot 10^5$ |
Zároveň, v druhej sade navyše platí, že každé mesto je prepojené káblami s práve dvoma inými mestami.
Input:
3 3 1
0 1
1 2
2 0
Output:
0 2 1
Tento vstup by sa mohol nachádzať v druhej sade. Keď odpojím od energie mesto číslo $0$, elektrina vie medzi elektrárňou (v meste $1$) a mestom $2$ prúdiť cez priamy kábel. Keď následne odpojíme mesto $2$, v meste $1$ stále je elektrina, lebo sa v ňom nachádza elektráreň. Napokon, keď vypneme elektráreň, ovplyvní to len to mesto, v ktorom sa nachádza.
Input:
3 2 2
0 1
1 2
Output:
0 1 2
Všimnite si, že nemôžeme najskôr odpojiť mesto $1$, keďže jediný spôsob, akým sa elektrina vie dostať do mesta $0$ je cez mesto $1$.
Input:
2 1 0
0 1
Output:
1 0
Nemáme na výber. Najskôr musíme vypnúť elektrinu v meste bez elektrárne
Input:
1 0 0
Output:
0
Máme jediné mesto, v ktorom je elektráreň.
Input:
8 11 2
1 0
2 0
1 6
7 1
6 7
2 6
3 2
4 5
5 2
3 4
2 1
Output:
0 7 6 1 5 4 3 2
Je možné že pre nejaký vstup existuje viacero správnych riešení. Akékoľvek z nich bude akceptované.
Nič nepokazíme hrubou silou: vygenerujeme všetky možné rôzne poradia
miest, tých je $n\times (n-1) \times \dots 1
= n!$, napríklad pomocou next_permutation alebo
itertools.permutations(), a pre každé takéto poradie
overíme, či spĺňa podmienku. Konkrétne si pre každé poradie vieme
postupne simulovať vypínanie miest.
Po každom odpojení prvých $1 \leq i\leq n - 1$ miest, si treba overiť, či všetky zostávajúce mestá sú stále spojené s elektrárňou. Ako prvé skontrolujeme, či sme náhodou neodpojili aj ju (ak áno, musí byť posledné vypnuté mesto v poradí).
Následne si vieme spustiť prehľadávanie grafu z elektrárne (DFS alebo BFS, viď kuchárku). Potom si len stačí overiť, či sme každé miesto už buď odpojili, alebo navštívili.
Pre každé možné poradie spustíme $n$-krát prehľadávanie celého grafu, ktoré zaberie v najhoršom prípade $O(n+m)$ času. Poradí je $n!$, takže časová zložitosť je $O(n! \cdot n \cdot (n + m))$. Pamäťová zložitosť je $O(n + m)$, keďže si musíme náš graf celý pamätať.
Všimnite si, že už len zisťovanie, či nejaké poradie funguje nám zaberie kvadratický čas – $O(n(n+m))$, čo je príliš veľa.
Skúsme, namiesto tipovania riešení a skúšania či fungujú, sa zamyslieť nad tým aké riešenia určite fungovať budú. Všimnime si, že mesto ktoré odstránime posledné vieme – je ním mesto, v ktorom je elektráreň. Nazvime ho $a_n$.
Čo vieme povedať o meste, ktoré odpojíme ako predposledné? Zjavne musí priamo susediť s elektrárňou, inak by stratilo prúd skôr než ho odpoja.
Zoberme si teda akékoľvek mesto súvisiace s elektrárňou, nazvime ho $a_{n-1}$.
Keď už máme mestá ktoré odpojíme ako posledné a predposledné ($a_n$ a $a_{n-1}$), nájdeme si mesto, ktoré odpojíme ako pred-predposledné, následne nájdeme to pred-pred-posledné, a tak ďalej.
Inak povedané, postupnosť zostavujeme odzadu. A teda namiesto toho, aby sme mestá odstraňovali, budeme mestá postupne pridávať. A chceme ich pridávať tak, aby sieť, ktorú si postupne budujeme, bola vždy súvislá, a nachádza sa v nej elektráreň.
Druhú podmienku, ako sme si už povedali hore, splníme tak, že poslednú vždy odstránime elektráreň. Ako splníme prvú podmienku? Jednoducho, keď pridávame mesto $a_i$, pridáme mesto ktoré sme ešte nepridali (nie je medzi $a_{i+1},\cdot a_n$), ale ktoré je priamo spojené s niektorým $a_{i+j}$.
Na základe tohto nápadu vieme implementovať riešenie bežiace v $O(n \cdot (n+m))$: najskôr nadstavíme $a_n=k$ (mesto s elektrárňou). Potom, budujeme postupnosť odzadu: ak už máme $a_n, a_{n-1},\dots, a_{i+1}$, si prejdeme susedov týchto miest, a nájdeme takého ktorý sa ešte v postupnosti nenachádza. Zakaždým prejdeme najviac $m$ hrán, takže dostaneme horeuvedenú časovú zložitosť.
Odtiaľto sme už len kúsok od vzorového riešenia. Všimnite si, že v riešení hore pozeráme na hrany viackrát. Stačilo by nám, napríklad, si vždy keď do postupnosti pridáme nové $a_i$, si jeho susedov pridať do fronty (alebo zásobníka). Potom, keď hľadáme nový vrchol do postupnosti, prejdeme si túto frontu (zásobník) vrcholov. Ak sme už vrchol dali do postupnosti, odstránime ho z fronty (zásobníku) a túto hranu už nikdy neuvidíme – nemusíme.
Ak sme ešte vrchol nevideli, nastavíme ho ako nový člen postupnosti a pridáme jeho susedov do fronty (zásobníku).
Takto robíme to, čo predtým, len efektívnejšie: ku každému susedovi sa dostaneme maximálne raz, ale zároveň, keďže je naša mestská sieť súvislá, určite naplníme celú postupnosť. Takto teda dostaneme riešenie bežiace v časovej a aj pamäťovej zložitosti $O(n + m)$.
A nielen to! Záležiac od toho akú dátovú štruktúru ste použili, naprogramovali ste vlastne BFS (v prípade fronty), alebo DFS (v prípade zásobníka)!
funguje?
Pozrime sa napokon na to, prečo je takáto postupnosť odpájania $a_1,\dots,a_n$ v súlade s požiadavkami zo zadania.
Zachovanie spojitosti: Keď vypíname mestá v reverznom poradí akom sme ich pridávali (čiže od $a_1$ po $a_k$), vždy vypneme najskôr tie mestá, ktoré boli pridané ako posledné pri budovaní siete. Ale pridávali sme ich predsa tak, aby mestá $a_i,a_{i+1},\dots,a_n$ boli súvislé, čiže vypnutie prvých $i$ miest nikdy nepreruší spojenie medzi elektrárňou a zvyšnými mestami.
Elektráreň posledná: Keďže sme začali prehľadávanie v elektrárni, tá sa automaticky stane prvým prvkom v poradí návštev, a teda posledným prvkom v poradí vypínania.
Úplné pokrytie: Elektrická sieť je súvislá, takže ak nám ostávajú ešte nejaké mestá, je medzi nimi, a už pridanými mestami nejaká hrana.
n,m,k = map(int,input().split())
susedia = [[] for _ in range(n)]
for _ in range(m):
a,b = map(int, input().split())
susedia[a].append(b)
susedia[b].append(a)
prejdeny = [False] * n
poradie = []
stack = [k]
prejdeny[k] = True
while stack:
vrchol = stack.pop()
poradie.append(vrchol)
for sused in susedia[vrchol]:
if not prejdeny[sused]:
prejdeny[sused] = True
stack.append(sused)
print(*reversed(poradie))
#include<bits/stdc++.h>
using namespace std;
#define FOR(i,n) for(int i=0;i<(int)n;i++)
#define FOB(i,n) for(int i=n;i>=1;i--)
#define MP(x,y) make_pair((x),(y))
#define ii pair<int, int>
#define lli long long int
#define ld long double
#define ulli unsigned long long int
#define lili pair<lli, lli>
#ifdef EBUG
#define DBG if(1)
#else
#define DBG if(0)
#endif
#define SIZE(x) int(x.size())
const int infinity = 2000000999 / 2;
const long long int inff = 4000000000000000999;
typedef complex<long double> point;
template<class T>
T get() {
T a;
cin >> a;
return a;
}
template <class T, class U>
ostream& operator<<(ostream& out, const pair<T, U> &par) {
out << "[" << par.first << ";" << par.second << "]";
return out;
}
template <class T>
ostream& operator<<(ostream& out, const set<T> &cont) {
out << "{";
for (const auto &x:cont) out << x << ", ";
out << "}";
return out;
}
template <class T, class U>
ostream& operator<<(ostream& out, const map<T,U> &cont) {
out << "{";
for (const auto &x:cont) out << x << ", ";
out << "}"; return out;
}
template <class T>
ostream& operator<<(ostream& out, const vector<T>& v) {
FOR(i, v.size()){
if(i) out << " ";
out << v[i];
}
out << endl;
return out;
}
bool ccw(point p, point a, point b) {
if((conj(a - p) * (b - p)).imag() <= 0) return false;
else return true;
}
void dfs(int v, vector<bool> &seen, vector<int> &poradie, vector<vector<int> >&hrany) {
seen[v] = true;
for (int w : hrany[v]) {
if (!seen[w]) dfs(w, seen, poradie, hrany);
}
poradie.push_back(v);
}
int main() {
cin.sync_with_stdio(false); cin.tie();
cout.sync_with_stdio(false); cout.tie();
int n = get<int>();
int m = get<int>();
int k = get<int>();
vector<int> poradie;
vector<bool> seen(n, false);
vector<vector<int> > hrany(n);
FOR(i, m) {
int a = get<int>();
int b = get<int>();
hrany[a].push_back(b); hrany[b].push_back(a);
}
dfs(k, seen, poradie, hrany);
FOR(i, n) {
cout << (i ? " " : "") << poradie[i];
}
cout << endl;
}
Korešpondenčný seminár z programovania zastrešuje občianske združenie Trojsten.
Trojsten, o.z.
FMFI UK, Mlynská dolina
842 48 Bratislava
Programátorská súťaž pre základoškolákov
Materiály a úlohy na výučbu programovania
Intenzívny programátorský zážitok v lete