Zadanie
Gašpar už od samej nudy naozaj nevie, čo robiť. Absentuje akákoľvek rozumná myšlienka. A tak otvorí tašku v kúte izby a rozmýšľa, čo by len mohol použiť. Kalkulačka? Stará plesnivá desiata? Zbierka úloh KSP? Vodná pištol? Lano? Lano!
Gašpar už vie, čo spraví. Natiahne lano medzi niektoré stromy v záhrade, bude sa po ňom prechádzať a naučí sa na lane robiť nejaké tie akrobatické kúsky.
Teraz však Gašpar stojí pred závažnou otázkou: ktoré dvojice stromov spojiť povrazom? Samozrejme, že natiahnuté povrazy sa nesmú križovať. Okrem toho sú z rôznych dôvodov niektoré dvojice stromov vylúčené. Popri tomto všetkom by Gašpar chcel vytvoriť, čo najviac spojení.
Pomôžte Gašparovi s týmto problémom!
Úloha
V záhrade sú dva záhony obsahujúce po \(n\) stromov. Stromy v ľavom záhone majú priradené čísla \(p_1, \dots, p_n\). Stromy v pravom záhone majú priradené čísla \(q_1, \dots, q_n\). Platí, že \(p_1, \dots, p_n\) a \(q_1, \dots, q_n\) sú permutáciami čísel \(1, \dots, n\).
Gašpar chce natiahnuť lano medzi niektorými dvojicami stromov tak, aby platilo:
- každé spojenie vedie medzi nejakým stromom z ľavého záhonu a nejakým stromom z pravého záhonu,
- na každý strom je napojené nanajvýš \(1\) lano,
- natiahnuté povrazy sa nekrižujú,
- ak sú stromy \(p_i\) a \(q_j\) spojené povrazom, tak platí \(|p_i-q_j| \leq 4\).
Zistite, koľko najviac spojení môže Gašpar vytvoriť.
Formát vstupu a výstupu
Na prvom riadku vstupu je číslo \(n\) – počet stromov v jednom záhone, \(1 \leq n \leq 300\,000\).
Druhý riadok vstupu obsahuje \(n\) čísel \(p_1, \dots, p_n\) – čísla stromov v prvom záhone. \(p_1, \dots, p_n\) sú permutáciou čísel \(1, \dots, n\).
Tretí riadok obsahuje \(n\) čísel \(q_1, \dots, q_n\) – čísla stromov v druhom záhone. \(q_1, \dots, q_n\) sú permutáciou čísel \(1, \dots, n\).
Vypíste jeden riadok obsahujúci jedno číslo – najväčší počet lán, ktoré môže Gašpar medzi stromami natiahnuť.
Hodnotenie
Vstupy sú rozdelené do 4 sád. Každá sada je hodnotená 2 bodmi. Platia v nich nasledujúce obmedzenia:
Sada | 1 | 2 | 3 | 4 |
---|---|---|---|---|
\(1 \leq n \leq\) | \(100\) | \(5000\) | \(100\,000\) | \(300\,000\) |
Príklad
Input:
6
6 2 1 3 4 5
2 4 6 5 3 1
Output:
5
V jednej z optimálnych konfigurácii sú spojené dvojice stromov \((6, 2), (2, 4), (1, 5), (3, 3)\) a \((5, 1)\).
Naivná dynamika
Definujeme \(dp[i][j]\) ako maximálny počet spojení, ak posledná dvojica, ktorú sme vybrali je \((i, j)\) (príslušné čísla stromov sú \((p_i, q_j\))). Ak dvojicu \((i, j)\) nie je možné vybrať (\(|p_i-q_j| > 4\)), definujeme \(dp[i][j]\) ako \(0\). Teraz je odpoveď jednoducho maximum \(dp[i][j]\) spomedzi všetkých \((i, j)\).
Ako spočítať \(dp\)? Prejdeme vzostupne všetky \(i\) od \(1\) po \(n\) a pre každé \(i\) prejdeme všetky \(j\) od \(1\) po \(n\). Ak \(|p_i-q_j| > 4\), tak \(dp[i][j] = 0\). Inak vyskúšame všetky možné predošlé dvojice, teda \((k, l)\) také, že \(k < i, l < j\). Odpoveď je jednoducho \(dp[i][j] = 1+ \max \lbrace dp[k][l] \rbrace\). Časová zložitosť je \(O(n^4)\) (máme \(n^2\) stavov a každý spracujeme v \(O(n^2)\)).
Vylepšená dynamika
Dvojice \((i, j)\), ktoré spĺňajú \(|p_i - q_j| \leq 4\) volajme validné. Všimnime si, že validných dvojíc je v skutočnosti málo. Pre každé \(i\) je ich najviac \(9\), celkovo teda \(O(n)\). To znamená, že vieme vyššie popísaný algoritmus vylepšiť hneď dvoma spôsobmi: - môžeme zredukovať počet stavov v našom dynamickom programovaní – stačia nám stavy zodpovedajúce validným dvojiciam; - pri počítaní \(dp\) nám stačí kontrolovať menej možných predošlých dvojíc.
Na začiatku si vygenerujeme všetky validné spojenia. Toto vieme spraviť v čase \(O(n)\) (napríklad priamočiaro za pomoci poľa \(q_{inv}\) takého, že \(q_{inv}[q_j] = j\).) Validné spojenia si očíslujeme od \(1\) po \(S\). Pre \(s \in \lbrace 1, \dots, S\rbrace\) definujeme \(dp[s]\) ako najväčší počet spojení, ktorý vieme dosiahnuť, ak posledné vybrané spojenie je \(s\).
Ako spočítať \(dp\)? Prechádzame cez všetky validné spojenia s číslom \(\tilde{s} \in \lbrace 1, \dots, S \rbrace\). Skontrolujeme, či je spojenie \(\tilde{s}\) naľavo od \(s\) (ak spojeniu \(s\) zodpovedá \((i, j)\) a spojeniu \(\tilde{s}\) zodpovedá \((\tilde{i}, \tilde{j})\), tak kontrolujeme, či \(\tilde{i} < i\) a zároveň \(\tilde{j} < j\) ). Potom \(dp[s] = 1 +\) maximum z príslušných hodnôt \(dp[\tilde{s}]\). Časová zložitosť sa zlepšila na \(O(n^2)\) (máme \(O(n)\) stavov a každý spracujeme v \(O(n)\)).
Vzorová dynamika
Kľúč k úspechu je zapracovať na počítaní \(dp[s]\) pre konkrétne \(s\). Validné dvojice budeme prechádzať v zotriedenom poradí, po skupinkách: najprv spracujeme tie, ktoré majú \(i = 1\), potom tie s \(i = 2\), atď. Vďaka tomuto bude jednoduché overovať, či \(\tilde{i} < i\). Pri počítaní \(dp[s]\) potrebujeme preveriť všetkých kandidátov na predošlú dvojicu. Kto sú reálni kandidáti? Sú to presne všetky validné dvojice \(\tilde{s}\) s \((\tilde{i}, \tilde{j})\) z predošlých skupiniek (\(\tilde{i} < i\)), pre ktoré \(\tilde{j} < j\). Potrebujeme zobrať maximum z hodnôt \(dp[\tilde{s}]\) pre týchto kandidátov. Na takéto veci je ako stvorený intervalový strom.
Vytvoríme si intervaláč, v ktorom si pre fixné \(\tilde{j}\) od \(1\) po \(n\) budeme udržovať maximálne \(dp[\tilde{s}]\) pre validné dvojice \(\tilde{s}\) zodpovedajúce \((\tilde{i}, \tilde{j})\) pre \(\tilde{s}\) zo skupiniek, ktoré sme už spracovali. (Ak teda práve spracovávame skupinku s nejakým fixným \(i\), tak hodnota v intervaláči pre fixné \(\tilde{j}\) bude maximum z hodnôt \(dp\) pre validné dvojice spomedzi \((1, \tilde{j}), \dots, (i-1, \tilde{j})\)). Intervaláč budeme updatovať vždy po spracovaní celej skupinky. Takýchto updatov bude po každej skupinke najviac \(9\) (toľko najviac dvojíc je v jednej skupinke) a intervaláč ich vykoná v \(O(\log n)\). No a pri spracovaní konkrétnej dvojice \(s\) zodpovedajúcej \((i, j)\) nám vlastne stačí spočítať maximum hodnôt na intervale \([0, j-1]\). Takéto otázky nám intervaláč zodpovie tiež v \(O(\log n)\).
Celková časová zložitosť tak bude \(O(n \log n)\) (prejdeme \(O(n)\) skupiniek, každú spracujeme v \(O(\log n)\)). Pamäťová zložitosť bude \(O(n)\).
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> vi;
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define FOR(i, x) for(auto &i : x)
#define pb push_back
struct Intervalac {
int offset;
<int> pole;
vector
(int N) {
Intervalac= 1;
offset while(offset <= N) offset *= 2;
.resize(2*offset, 0);
pole}
void update(int poz, int hod) {
int v = poz+offset;
while(v != 0) {
[v] = max(pole[v], hod);
pole/= 2;
v }
return;
}
int maxi(int a, int b, int v, int l, int r) {
if(l >= b || r <= a) return 0;
if(a <= l && r <= b) return pole[v];
return max(maxi(a, b, 2*v, l, (l+r)/2), maxi(a, b, 2*v+1, (l+r)/2, r));
}
int maxi(int a, int b) {
return maxi(a+offset, b+offset, 1, offset, 2*offset);
}
};
int main() {
::sync_with_stdio(false);
iosint N;
>> N;
cin <int> poz(N);
vector(n, 0, N) {
REPint id;
>> id;
cin --;
id[id] = n;
poz}
(N);
Intervalac Interint vys = 0;
(n, 0, N) {
REPint id;
>> id;
cin --;
id, hod;
vi hrany(jd, max(0, id-4), min(N, id+5)) hrany.pb(jd);
REP(jd, hrany) {
FOR.pb(1+Inter.maxi(0, poz[jd]));
hod= max(vys, hod.back());
vys }
(i, 0, hrany.size()) {
REPint jd = hrany[i];
.update(poz[jd], hod[i]);
Inter}
}
<< vys << "\n";
cout return 0;
}
Diskusia
Tu môžte voľne diskutovať o riešení, deliť sa o svoje kusy kódu a podobne.
Pre pridávanie komentárov sa musíš prihlásiť.