Optymalizacja zadania Skrzaty

0

Cześć mam takie zadanie:
Skrzaty
Limit pamięci: 64 MB

Zły smok Bitol najechał krainę skrzatów i wziął w niewolę jej mieszkańców. Przydzielił każdemu z n skrzatów inne stanowisko pracy i, samemu ległszy na stercie skradzionych kosztowności, jął leniwie nadzorować ich mozolne trudy.

Jako że Bitol jest wyjątkowo gnuśnym smokiem, nie obserwuje jednocześnie wszystkich poddanych. Zamiast tego cały czas przygląda się uważnie tylko skrzatom pracującym przy pewnej grupie stanowisk. W tym czasie wszystkie nieobserwowane przezeń skrzaty mogą spotykać się oraz dowolnie zamieniać się miejscami (Bitol nie jest w stanie zapamiętać, przy którym stanowisku pracował który skrzat). Co godzinę smok obraca głowę i zaczyna obserwować inny podzbiór skrzatów.

Skrzat Bajtazyl, któremu smok przydzielił stanowisko 1, chce zmobilizować towarzyszy niedoli do przeciwstawienia się Bitolowi. W tym celu musi najpierw spotkać się z sędziwym skrzatem Bajtomirem, któremu smok przydzielił stanowisko n. Przed Bajtazylem stoi zatem wyzwanie: odpowiednio zamieniając się z innymi skrzatami miejscami, winien jak najszybciej doprowadzić do sytuacji, w której on sam, ani stanowisko przy którym stoi aktualnie nasz śmiałek, ani stanowisko nie byłyby obserwowane przez smoka.

Twoim zadaniem jest ustalenie, kiedy najwcześniej może dojść do spotkania. Na szczęście wiadomo, że za godzin smok uśnie i wówczas wszystkie skrzaty będą w stanie komunikować się swobodnie.
Wejście

W pierwszym wierszu standardowego wejścia znajdują się dwie liczby całkowite n i m (1 <= n, m <= 1 000 000) oznaczające odpowiednio liczbę skrzatów oraz liczbę godzin pozostałych do czasu, aż Bitol zaśnie. W następnych wierszach znajdują się opisy grup stanowisk obserwowanych przez smoka w kolejnych godzinach, po jednym w wierszu. Na opis i-tej grupy stanowisk składa się liczba całkowita ki (1 <= ki <= n) oznaczająca liczbę obserwowanych stanowisk oraz uporządkowanych rosnąco liczb całkowitych ze zbioru {1...n} oznaczających numery obserwowanych stanowisk. Wszystkie liczby w wierszu poodzielane są pojedynczymi odstępami.

Możesz założyć, że k1 + k2 +km <= 2 000 000 .
Wyjście

W pierwszym i jedynym wierszu standardowego wyjścia Twój program powinien wypisać jedną liczbę całkowitą ze zbioru {0....m} - najmniejszą liczbę godzin, po której Bajtazyl może dotrzeć do Bajtomira.
Przykład

Dla danych wejściowych:

5 4
3 1 3 4
2 3 5
3 1 2 3
2 1 2

poprawną odpowiedzią jest:

2

Dla danych wejściowych:

6 2
4 2 3 4 5
6 1 2 3 4 5 6

poprawną odpowiedzią jest:

0

Dla danych wejściowych:

10 4
1 1
2 9 10
7 1 3 4 7 8 9 10
2 1 10

poprawną odpowiedzią jest:

4

Wyjaśnienie do przykładu:

W pierwszym z powyższych przykładów podczas pierwszej godziny swej wyprawy Bajtazyl nie może opuścić stanowiska o numerze , gdyż jest ono obserwowane przez smoka. Podczas drugiej godziny może on zamienić się miejscami ze skrzatem przy stanowisku o numerze . Dzięki temu dopiero na początku trzeciej godziny smok Bitol odwróci głowę ku stanowiskom o numerach , a Bajtazyl będzie mogł spotkać się z Bajtomirem.

W drugim z powyższych przykładów do spotkania może dojść natychmiast, gdyż w pierwszej godzinie smok nie patrzy na stanowiska Bajtazyla i Bitomira.

W trzecim przykładzie do spotkania może dojść dopiero wtedy, gdy Bitol uśnie.

I Mam taki kod

#include <bits/stdc++.h>

using namespace std;

bool can_swap(vector<bool> & possible_pos, vector<bool> & watched_pos){
    for (int i = 0; i < possible_pos.size(); i++){
        if (possible_pos[i] == true && watched_pos[i] == false)
            return true;
    }
    return false;

}
void update_poss_pos(vector<bool> & possible_pos, vector<bool> & watched_pos){
    for (int i = 1; i < possible_pos.size(); i++){
        if (watched_pos[i] == false)
            possible_pos[i] = true;
    }
}


int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr);

    int n, m, k, ans;
    bool if_can_swap;
    cin >> n >> m;
    ans = m;

    vector<bool> possible_pos(n+1, false);
    possible_pos[1] = true;
    vector<vector<int>> dragon_watches(m);
    for (int i = 0; i < m; i++){
        cin >> k;
        dragon_watches[i].resize(k);
        for (int j = 0; j < k; j++)
            cin >> dragon_watches[i][j];
    }
    for (int i = 0; i < m; i++){
        vector<bool> watched_pos(n+1, false);
        for (int pos : dragon_watches[i])
            watched_pos[pos] = true;

        if_can_swap = can_swap(possible_pos, watched_pos);
        if (watched_pos[n] == false && if_can_swap){
            ans = i;
            break;
        }
        if (if_can_swap)
            update_poss_pos(possible_pos, watched_pos);
    }
    cout << ans;

  return 0;
}

niestety jest za wolny dla kilku testów, próbowałem też z setami ale czasy wychodziły jeszcze gorsze, jak zoptymalizować mój algorytm >

2

Nie sądzę, że musisz zapisywać wszystkie "tury" do vectora. Wydaje się, że te wszystkie potrzebne rzeczy możesz sprawdzić w trakcie wczytywania wejścia. Poza tym na pewno dużo tracisz robiąc resize.

1

Masz algorytm o złożoności O(mn), dla n, m <= 10^6. W najgorszym wypadku O(10^12). Masz też podpowiedź, że suma ilości obserwowanych pozycji we wszystkich m turach jest mniejsza od 2*10^6. A więc jest rzędu O(n).
Założyłbym, że trzeba zbić tą złożoność.

Można próbować O(m logn) korzystając na przykład z drzew segmentowych ;). Ciąg obserwowanych pozycji przecież można zamienić na ciąg nie obserwowanych przedziałów. W drzewie przedziałowym można zaznaczać te przedziały, które są osiągalne, zaczynając od 1. Jeżeli nowy przedział łączyłby się z jakimś przedziałem osiągalnym to oznaczamy go jako osiągalny. Gdy pozycja n (czyli przedział [n, n] będzie osiągalny, to algorytm się kończy.
Drzewo (przedział, przedział), (=, max) powinno dać radę.

EDIT: Wrzuciłem to do drzewa przedziałowego i przechodzi wszystkie testy.

0

@tsz spróbowałem tak:

#include <bits/stdc++.h>

using namespace std;

int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr);

    int n, m, k, ans, i, pos, watch_poss_pos, number_of_poss_pos;
    bool if_can_swap;
    cin >> n >> m;
    ans = m;
    bool * possible_pos = new bool[n+1];
    possible_pos[1] = true;
    bool * watched_pos = new bool[n+1];
    for (int u = 2; u <= n; u++)
        possible_pos[u] = false;

    number_of_poss_pos = 1;
    i = 0;
    while ( i < m && ans == m){
        for (int u = 0; u <= n; u++)
            watched_pos[u] = false;
        watch_poss_pos = 0;
        cin >> k;
        for (int j = 0; j < k; j++){
            cin >> pos;
            watched_pos[pos] = true;
            if (possible_pos[pos] == true)
                watch_poss_pos++;
        }

        if (watch_poss_pos < number_of_poss_pos && watched_pos[n] == false){
            ans = i;
        }

        if (watch_poss_pos < number_of_poss_pos){
            for(int x = 1; x <= n; x++){
                if (watched_pos[x] == false && possible_pos[x] == false){
                    possible_pos[x] = true;
                    number_of_poss_pos++;
                }

            }
        }
        i++;
    }
    while (i < m){
        cin >> k;
        for (int j = 0; j < k; j++)
            cin >> pos;
        i++;
    }
    cout << ans;

  return 0;
}

Polepszyło to mój czas, jednak algorytm dalej za wolny idę znowu oglądać pana Radoszewskiego ;)

0

W drzewie przedziałowym można zaznaczać te przedziały, które są osiągalne, zaczynając od 1. Jeżeli nowy przedział łączyłby się z jakimś przedziałem osiągalnym to oznaczamy go jako osiągalny.

Nie rozumiem, w przypadku gdy jedynym możliwym przedziałem jest 1 i gdy smok obserwuje pozycje 2 3 5, osiągalny przedział to [4, 4] który nie łączy się z przedziałem [1, 1] więc według tego co napisałeś to nie dodajemy go, a przecież powinniśmy dodać przedział [4, 4] do osiągalnych

0

Okazało się że nie trzeba tworzyć drzewa segmentowego, nie wiem jak ale wgl nie zwróciłem uwagi że pozycje oglądane przez smoka są posortowane, znalazłem kod w internecie:

/**
 * VI Olimpiada Informatyczna Gimnazjalistów
 * Zadanie: Skrzaty
 * Wynik 100/100
 * Wykonał: Marcin Wątroba
 * http://main.edu.pl/pl/archive/oig/6/skr
 */

#include <cstdlib>
#include <cstdio>
#include <vector>
#include <iostream>

using namespace std;

struct przed {
    int p;
    int k;
};

int wbez(int a) {
    if (a > 0)return a;
    else return -1 * a;
}

bool czy_wsp(int *tab, int dl, vector<przed> prz[1]) {
    int ltab = 0; // licznik tablicy
    int lvec = 0; // licznik vectora
    bool wynik = false;
    int ostwprz = -1;
    for (ltab; ltab < dl; ltab++) {
        //DOBRA FUNKCJA
        while (true) {
            if (lvec == prz[0].size()) {
                lvec--;
                break;
            } else if (tab[ltab] >= prz[0][lvec].p && tab[ltab] <= prz[0][lvec].k)
                break;
            else if (tab[ltab] < prz[0][lvec].p)
                break;
            if (ltab == 0)
                wynik = true;
            else if (-1 * tab[ltab - 1] != prz[0][lvec].k && tab[ltab - 1] < 0)
                wynik = true;
            else if (tab[ltab] > prz[0][lvec].k && tab[ltab - 1] < prz[0][lvec].k && tab[ltab - 1] > 0)
                wynik = true;
            lvec++;
        }
        if (tab[ltab] >= prz[0][lvec].p && tab[ltab] <= prz[0][lvec].k) {
            if (tab[ltab] - 1 >= prz[0][lvec].p && ltab > 0)
                if (tab[ltab] - 1 != tab[ltab - 1] * (-1))
                    wynik = true;
            tab[ltab] *= -1;
        }
    }
    if (wbez(tab[dl - 1]) < prz[0][prz[0].size() - 1].k) {
        wynik = true;
    }
    for (int a = 0; a < dl; a++)
        if (tab[a] < 0)tab[a] = 0;
    return wynik;
}

void nzbior(int *tab, int dl, vector<przed> prz[1], int n) {
    vector<przed> tym;
    int ltab;
    przed pom;
    pom.p = 1;
    for (int ltab = 0; ltab <= dl; ltab++) {
        if (ltab == 0)
            pom.p = 1;
        else
            pom.p = tab[ltab - 1] + 1;
        while (ltab != dl && tab[ltab] == 0)
            ltab++;
        if (ltab != dl)
            pom.k = tab[ltab] - 1;
        else
            pom.k = n;
        if (pom.p <= pom.k) {
            tym.push_back(pom);
        }
    }
    prz[0].assign(tym.begin(), tym.begin() + tym.size());
}

int main(int argc, char *argv[]) {
    int n, m;
    scanf("%d%d", &n, &m);
    int *dl = new int[m];
    int **obs = new int *[m];
    vector<przed> prz[1];
    przed pom = {1, 1};
    prz[0].push_back(pom);
    int i;
    bool wyn = false;
    for (i = 0; i < m; i++) {
        scanf("%d", &dl[i]);
        obs[i] = new int[dl[i]];
        for (int a = 0; a < dl[i]; a++)
            scanf("%d", &obs[i][a]);
        if (czy_wsp(obs[i], dl[i], prz)) {
            nzbior(obs[i], dl[i], prz, n);
        }
        if (prz[0][prz[0].size() - 1].k == n) {
            cout << i;
            wyn = true;
            break;
        }
    }

    if (!wyn)
        cout << m;

    return EXIT_SUCCESS;
}

trochę mi zajęło żeby ogarnąć o co tutaj wgl chodzi, ale pierwsza funkcja sprawdza czy można stworzyć nowy zbiór i zaznacza wszystkie obserwowane z możliwych pozycji (wartość * -1) a druga tworzy nowy zbiór dostępnych przedziałów, spróbowałem napisać swój, wydaje mi się bardziej czytelny program, żeby lepiej zrozumieć jak to zadanie rozwiązać

#include <bits/stdc++.h>

using namespace std;

class Interval{
public:
    int beg;
    int end;

    Interval(int beg=0, int end=0){
        this->beg = beg;
        this->end = end;
    }
};

void show_poss_inters(vector<Interval> v){
    cout << "[" << " ";
    for (Interval i : v)
        cout << "(" << i.beg << "," << " " << i.end << ")" << " ";
    cout << "]" << endl;
}

void show_d_w(vector<int> & d_w, int d_w_len){
    for (int i = 0; i < d_w_len; i++)
        cout << d_w[i] << " ";
    cout << endl;
}
bool can_meet(vector<int> & drag_watch, int d_w_len, vector<Interval> & poss_in, int p_i_len){
    if (drag_watch[d_w_len - 1] < poss_in[p_i_len - 1].end)
        return true;

    int p_i_iter = 0;

    for (int d_w_iter = 0; d_w_iter < d_w_len; d_w_iter++){
        while (p_i_iter < p_i_len){
            if (drag_watch[d_w_iter] >= poss_in[p_i_iter].beg && drag_watch[d_w_iter] <= poss_in[p_i_iter].end){
                if (drag_watch[d_w_iter] - 1 >= poss_in[p_i_iter].beg && d_w_iter > 0)
                    if (drag_watch[d_w_iter] - 1 != abs(drag_watch[d_w_iter - 1]))
                        return true;
                drag_watch[d_w_iter] *= -1;
            }
            else if (drag_watch[d_w_iter] > poss_in[p_i_iter].beg){
                if (d_w_iter == 0)
                    return true;
                if (abs(drag_watch[d_w_iter - 1]) != poss_in[p_i_iter].end && drag_watch[d_w_iter - 1] < 0)
                    return true;
                else if (drag_watch[d_w_iter] > poss_in[p_i_iter].end &&
                         drag_watch[d_w_iter - 1] < poss_in[p_i_iter].end && drag_watch[d_w_iter - 1] > 0)
                    return true;
            }
            p_i_iter++;
        }
        p_i_iter--;
    }
    return false;
}

void select_all_belonging_to_poss_intervals(vector<int> & drag_watch, int d_w_len, vector<Interval> & poss_in, int p_i_len){
    int p_i_iter = 0;
    for (int d_w_iter = 0; d_w_iter < d_w_len; d_w_iter++){
        while (p_i_iter < p_i_len){
            if (drag_watch[d_w_iter] < 0 || drag_watch[d_w_iter] >= poss_in[p_i_iter].beg && drag_watch[d_w_iter] <= poss_in[p_i_iter].end){
                drag_watch[d_w_iter] = 0;
                break;
            }
            p_i_iter++;
        }
    }
}

void update_poss_inters(vector<int> & drag_watch, int d_w_len, vector<Interval> & poss_in, int p_i_len, int last_gnome){
    poss_in.clear();
    select_all_belonging_to_poss_intervals(drag_watch, d_w_len, poss_in, p_i_len);
    if (drag_watch[d_w_len - 1] != last_gnome){
        poss_in.push_back(Interval(1, last_gnome));
        return;
    }
    Interval helper;
    int d_w_iter = 0;
    while (d_w_iter <= d_w_len){
        if (d_w_iter == 0)
            helper.beg = 1;
        else
            helper.beg = drag_watch[d_w_iter - 1] + 1;
        while (d_w_iter != d_w_len && drag_watch[d_w_iter] == 0)
            d_w_iter++;
        if (d_w_iter != d_w_len)
            helper.end = drag_watch[d_w_iter] - 1;
        else
            helper.end = last_gnome;
        if (helper.beg <= helper.end)
            poss_in.push_back(helper);
        d_w_iter++;
    }

}



int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(nullptr);
    int n, m, k, ans;
    cin >> n >> m;
    ans = m;
    vector<vector<int>> dragon_watches(m);
    vector<Interval> poss_inters;
    poss_inters.push_back(Interval(1,1));
    for (int i = 0; i < m; i++){
        cin >> k;
        dragon_watches[i].reserve(k);
        for (int j = 0; j < k; j++)
            cin >> dragon_watches[i][j];
        if (can_meet(dragon_watches[i], k, poss_inters, poss_inters.size()))
            update_poss_inters(dragon_watches[i], k, poss_inters, poss_inters.size(), n);
        if (poss_inters[poss_inters.size() - 1].end == n){
            ans = i;
            break;
        }

    }
    cout << ans;
  return 0;
}

niestety nie przechodzi wszystkich testów (choć ma niezłą szybkość, ostatni test rozwiązuje dobrze a jego czas to 1.00s/3.00s), gdzie robię błąd ?

1

No, ok. Z drzewem wygląda tak:

#include <algorithm>
#include <iostream>
#include <limits>
#include <vector>
using namespace std;

constexpr inline uint64_t ceil_pow2(uint64_t x) {
    uint64_t power = 1;
    while(power < x)
        power *= 2;
    return power;
}

class SegmentTree {
    struct Node { int val; int max; };
    vector<Node> nodes_;
    int lo_;
    int hi_;

public:
    SegmentTree(int lo, int hi) {
        const size_t count = ceil_pow2(hi - lo);
        nodes_.resize(2 * count, Node{ 0, numeric_limits<int>::min() });
        lo_ = lo;
        hi_ = lo_ + count;
    }

    inline void set(int lo, int hi, int val) {
        set(lo, hi, 1, lo_, hi_, val);
    }

    inline int query_max(int lo, int hi) const {
        return query_max(lo, hi, 1, lo_, hi_);
    }

private:
    void set(int lo, int hi, int node, int node_lo, int node_hi, int val) {
        const auto node_mid = node_lo + (node_hi - node_lo) / 2;

        if (lo <= node_lo && node_hi <= hi) {
            nodes_[node].val = val;
            nodes_[node].max = max(nodes_[node].max, val);
            return;
        }

        if (lo < node_mid) {
            set(lo, hi, 2*node, node_lo, node_mid, val);
            nodes_[node].max = max(nodes_[node].max, nodes_[2*node].max);
        }
        if (node_mid < hi) {
            set(lo, hi, 2*node + 1, node_mid, node_hi, val);
            nodes_[node].max = max(nodes_[node].max, nodes_[2*node + 1].max);
        }
    }

    int query_max(int lo, int hi, int node, int node_lo, int node_hi) const {
        const auto node_mid = node_lo + (node_hi - node_lo) / 2;

        if (lo <= node_lo && node_hi <= hi) {
            return nodes_[node].max;
        }

        int result = nodes_[node].val;
        if (lo < node_mid)
            result = max(result, query_max(lo, hi, 2*node, node_lo, node_mid));
        if (node_mid < hi)
            result = max(result, query_max(lo, hi, 2*node + 1, node_mid, node_hi));
        return result;
    }
};

bool move_exist(const vector<int> &watched, const SegmentTree &reachable) {
    for (size_t i = 1; i < watched.size(); ++i) {
        const auto a = watched[i - 1] + 1;
        const auto b = watched[i];
        if (a < b && (reachable.query_max(a, b) > 0)) {
            return true;
        }
    }
    return false;
}

void connect_reachable(const vector<int> &watched, SegmentTree &reachable) {
    for (size_t i = 1; i < watched.size(); ++i) {
        const auto a = watched[i - 1] + 1;
        const auto b = watched[i];
        if (a < b) {
            reachable.set(a, b, 1);
        }
    }
}

int main(int argc, char const *argv[]) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    size_t n, m;
    cin >> n >> m;

    SegmentTree reachable{1, (int)n + 1};
    reachable.set(1, 2, 1);

    vector<int> watched;
    watched.reserve(2 * n);

    size_t iter;
    for (iter = 0; iter < m; iter++) {
        size_t num_watched;
        cin >> num_watched;

        watched.resize(num_watched + 2);
        watched[0] = 0; // start guardian
        for (size_t i = 1; i <= num_watched; i++) {
            cin >> watched[i];
        }
        watched[num_watched + 1] = (int)n + 1; // end guardian

        if (move_exist(watched, reachable)) {
            connect_reachable(watched, reachable);
        }

        if (reachable.query_max(n, n + 1) > 0 && watched[num_watched] != n) {
            break;
        }
    }

    cout << iter << endl;

    return 0;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1