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;
    vector<int> pole;

    Intervalac(int N) {
        offset = 1;
        while(offset <= N) offset *= 2;
        pole.resize(2*offset, 0);
    }

    void update(int poz, int hod) {
        int v = poz+offset;
        while(v != 0) {
            pole[v] = max(pole[v], hod);
            v /= 2;
        }
        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() {
    ios::sync_with_stdio(false);
    int N;
    cin >> N;
    vector<int> poz(N);
    REP(n, 0, N) {
        int id;
        cin >> id;
        id--;
        poz[id] = n;
    }
    Intervalac Inter(N);
    int vys = 0;
    REP(n, 0, N) {
        int id;
        cin >> id;
        id--;
        vi hrany, hod;
        REP(jd, max(0, id-4), min(N, id+5)) hrany.pb(jd);
        FOR(jd, hrany) {
            hod.pb(1+Inter.maxi(0, poz[jd]));
            vys = max(vys, hod.back());
        }
        REP(i, 0, hrany.size()) {
            int jd = hrany[i];
            Inter.update(poz[jd], hod[i]);
        }
    }
    cout << vys << "\n";
    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ť.