• 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
269 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.)
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,540 p.)
Aaaa czekaj, czyli ty chcesz kopiować w rekurencji bitset dp?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (56,980 p.)
Na każdym poziomie rekurencji pewnie będzie potrzebna nowa kopia bitseta
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
Ale jak chcesz w szybkim czasie połączyć te dp-ki z dwóch przedziałów?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (56,980 p.)
A sorki źle powiedziałem. Tu będzie samo dziel bez zwyciężaj
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
Co oznacza samo dziel bez zwyciężaj?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (56,980 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,540 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 (56,980 p.)
wystarczy trzymać tego bitseta chyba
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
I jak odpowiadać na zapytania?
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (56,980 p.)
jakie zapytania?
komentarz 12 czerwca 2023 przez pasjonat_algorytmiki Pasjonat (19,540 p.)
czy da się zrobić K, bez A[i]
komentarz 12 czerwca 2023 przez Whistleroosh Maniak (56,980 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,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ź 251 wizyt
0 głosów
2 odpowiedzi 259 wizyt
pytanie zadane 5 lutego 2023 w C i C++ przez polandonion Mądrala (7,040 p.)
0 głosów
1 odpowiedź 277 wizyt

92,620 zapytań

141,471 odpowiedzi

319,796 komentarzy

62,003 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!

...