• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Zadanie Kolekcjoner Monet, programowanie dynamiczne

0 głosów
1,045 wizyt
pytanie zadane 10 czerwca 2023 w Algorytmy przez pasjonat_algorytmiki Pasjonat (19,560 p.)

Mam problem z takim zdaniem:

Pewnie wejdzie tu jakieś dp, a dokładniej plecak. Napisałem kod w O(N^2 * K), zakładając, że nie używam danego przedmiotu i robię prosty plecak z pozostałymi w celu sprawdzenia, czy mogę ułożyć K pozostałymi. Dostaję 50pkt. Bardzo prosiłbym o hinta jak to przyśpieszyć.

Kod:

#include <iostream>
#include <vector>

using namespace std;

int n = 0, k = 0, wczytana_liczba = 0, cnt = 0;
vector<bool> visited;
vector<bool> dp;
vector<int> A;
vector<int> sprawdzane;
vector<int> do_dodania;

inline bool czy_pasuje(int x)
{
    dp.assign(k+1,false);
    dp[0] = true;
    sprawdzane = vector<int>();
    sprawdzane.push_back(0);
    for (int i = 0; i < n; ++i)
    {
        if (i == x)
            continue;
        do_dodania = vector<int>();
        for (int j = sprawdzane.size()-1; j >= 0; --j)
        {
            int val = sprawdzane[j];
            if (val + A[i] <= k and dp[val] == true and dp[val+A[i]] == false)
            {
                do_dodania.push_back(val+A[i]);
                dp[val+A[i]] = true;
            }
            if (dp[k] == true)
                return true;
        }
        for (int j = do_dodania.size()-1; j >= 0; --j)
            sprawdzane.push_back(do_dodania[j]);
    }
    return false;
}

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

    cin >> n >> k;

    visited.assign(k+1,false);
    for (int i = 0; i < n; ++i)
    {
        cin >> wczytana_liczba;
        visited[wczytana_liczba] = true;
    }

    for (int i = 1; i <= k; ++i)
        if (visited[i] == true)
            A.push_back(i);

    for (int i = 0; i < n; ++i)
    {
        if (czy_pasuje(i) == false)
        {
            cout << A[i] << ' ';
            ++cnt;
        }
    }

    if (cnt == 0)
        cout << "OK";
    cout << '\n';

    return 0;
}

Z góry dziękuję za pomoc i poświęcony czas!

komentarz 10 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Jedyne spostrzerzenie jakie poczyniłem w kierunku czegoś szybszego to to, że jak chcemy ułożyć sumę K, to użyjemy co najwyżej sqrt(K) przedmiotów.
komentarz 11 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)

Mam kod na 96pkt, to samo tylko, że na bitsecie robię, i mam O(N^2 * K / 32), nie wiem jak to przyśpieszyć. Kod:

#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>

using namespace std;

int n = 0, k = 0, wczytana_liczba = 0, cnt = 0;
vector<int> A;
vector<int> ile;

const int SIZE = 10'000;
bitset<SIZE+5> B;

inline bool czy_pasuje(int x)
{
    B = bitset<SIZE+5>();
    B.set(0);
    for (int i = 0; i < n; ++i)
    {
        if (i == x)
            continue;
        B = (B | (B << A[i]));
        if (B[k] == 1)
            return true;
    }
    return false;
}

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

    cin >> n >> k;

    ile.assign(k+1,0);
    for (int i = 0; i < n; ++i)
    {
        cin >> wczytana_liczba;
        ++ile[wczytana_liczba];
    }

    for (int i = 1; i <= k; ++i)
        for (int j = 0; j < ile[i]; ++j)
            A.push_back(i);

    for (int i = 0; i < n; ++i)
    {
        if (czy_pasuje(i) == false)
        {
            printf("%d ",A[i]);
            ++cnt;
        }
    }

    if (cnt == 0)
        printf("OK\n");

    return 0;
}

 

1 odpowiedź

+1 głos
odpowiedź 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
wybrane 14 czerwca 2023 przez pasjonat_algorytmiki
 
Najlepsza

Jest pewien trick, który pozwala liczyć szybko dp pomijając jeden element. Korzysta z dziel i zwyciężaj. Działa mniej więcej tak:

Bierzesz wszystkie monety, dzielisz je na pół. Na jednej połowie liczysz dp (czyli to co robisz tutaj B = (B | (B << A[i]))) a drugą znowu dzielisz na pół i powtarzasz rekurencyjnie. W końcu nie będzie można dzielić na pół bo zostanie tylko jeden element. Czyli dostaniesz dp pomijające ten jeden element.

Jeśli dobrze licze to będzie działać O(nlogn*k)

komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
1 - A jak usunąć element z dp?

2 - O ile ten twój pomysł działa, to nie powinien być O(n lg n * k / 32) ?
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Aaaa czekaj, czyli ty chcesz kopiować w rekurencji bitset dp?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
Na każdym poziomie rekurencji pewnie będzie potrzebna nowa kopia bitseta
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Ale jak chcesz w szybkim czasie połączyć te dp-ki z dwóch przedziałów?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
A sorki źle powiedziałem. Tu będzie samo dziel bez zwyciężaj
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Co oznacza samo dziel bez zwyciężaj?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
Chodzi o to, żeby działało podobnie jak merge sort ale bez tej częsci w której się merguje.
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
i w danym wierzchołku w drzewie binarnym trzymamy jakie wartości da się zrobić liczbami z jego przedziału?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
wystarczy trzymać tego bitseta chyba
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
I jak odpowiadać na zapytania?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
jakie zapytania?
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
czy da się zrobić K, bez A[i]
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
Nie ma zapytań. Na ostanim poziomie rekurencji będziesz wiedziałe czy się da czy nie. Wystarczy sprawdzić B[k]
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Mamy drzewo binarne, takie jak w przedziałowcu. 2N-1 wierzchołków, w każdym trzymamy bitseta który mówi nam, jakie da się zrobić z liczb w przedziale, jak kontroluje. Przy takim założeniu, jak chcemy poprostu B[k] sprawdzić?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
Nie ma drzewa. To ma działać podobnie jak merge sort. Wywołujesz jedną funkcję rekurenycjną, która wszystko policzy. Jak nie będziesz mógł dzielisz przedziału na pół, bo został jeden element to sprawdzasz B[k]
komentarz 14 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)
Tak masz rację. To działa w O(N lg N * K / 32) o ile się nie mylę
komentarz 14 czerwca 2023 przez Whistleroosh Maniak (57,400 p.)
Jak skorzystasz z bitestów a nie tradycyjnego dp to pewnie tak
1
komentarz 14 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,560 p.)

No i weszla 100pkt, super rozwiazanie. Chyba autorowi zadania chodzilo o dopilowanie bitseta, bo ten solv robi sie zazwyczaj 0.01 / 0.50s xD. Kod jest bardzo prosty, dokladnie to co mówisz. 

#include <iostream>
#include <vector>
#include <algorithm>
#include <bitset>

using namespace std;

int n = 0, k = 0, cnt = 0;
const int SIZE = 10000 + 5;
vector<int> A;
vector<bool> wyn;

inline void dziel_i_rzadz(int l, int p, bitset<SIZE> B)
{
    if (l == p)
    {
        if (B[k] == 1)
            wyn[l] = false;
        return;
    }
    int srodek = (l+p) / 2;
    bitset<SIZE> B_lewy = B, B_prawy = B;
    for (int i = l; i <= srodek; ++i)
        B_lewy = (B_lewy | (B_lewy << A[i]));
    for (int i = srodek+1; i <= p; ++i)
        B_prawy = (B_prawy | (B_prawy << A[i]));
    dziel_i_rzadz(l,srodek,B_prawy);
    dziel_i_rzadz(srodek+1,p,B_lewy);
}

int main()
{
    // O(N lg N * K / 32), dziel i rzadz z plecakiem.
    ios_base::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);

    cin >> n >> k;

    A.assign(n,-1);
    for (int i = 0; i < n; ++i)
        cin >> A[i];
    sort(A.begin(),A.end());

    wyn.assign(n,true);
    bitset<SIZE> B(0);
    B.set(0);
    dziel_i_rzadz(0,n-1,B);

    for (int i = 0; i < n; ++i)
    {
        if (wyn[i] == true)
        {
            cout << A[i] << ' ';
            ++cnt;
        }
    }

    if (cnt == 0)
        cout << "OK" << '\n';
    else
        cout << '\n';

    return 0;
}

Dzięki!

Podobne pytania

0 głosów
1 odpowiedź 797 wizyt
0 głosów
2 odpowiedzi 689 wizyt
pytanie zadane 5 lutego 2023 w C i C++ przez polandonion Dyskutant (7,710 p.)
0 głosów
1 odpowiedź 659 wizyt

93,742 zapytań

142,680 odpowiedzi

323,299 komentarzy

63,328 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Twierdza Linux. Bezpieczeństwo dla dociekliwych

Aby uzyskać rabat -10%, użyjcie kodu pasja-linux, wpisując go w specjalne pole w koszyku.

...