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

Zadanie Kolekcjoner Monet, programowanie dynamiczne

Object Storage Arubacloud
0 głosów
266 wizyt
pytanie zadane 10 czerwca 2023 w Algorytmy przez pasjonat_algorytmiki Pasjonat (19,540 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,540 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,540 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 (56,980 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,540 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 (56,980 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,540 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 (56,980 p.)
Jak skorzystasz z bitestów a nie tradycyjnego dp to pewnie tak
1
komentarz 14 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,540 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ź 247 wizyt
0 głosów
2 odpowiedzi 234 wizyt
pytanie zadane 5 lutego 2023 w C i C++ przez polandonion Mądrala (7,040 p.)
0 głosów
1 odpowiedź 275 wizyt

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 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.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...