Idi na tekst

2 - Kompresovani niz

Vremensko ograničenje Memorijsko ograničenje
500ms 256MB

Članovi komisije su odlučili da takmičarima olakšaju čitanje tekstova, te je tekst ovog zadatka napisan formalno:

Dat je niz A sa N elemenata i broj K. Na nizu se primenjuju operacije sledećeg tipa, sve dok je to moguće:

  • Izabere se podniz uzastopnih elemenata između pozicija L i R (uključujući i te dve pozicije), dužine tačno K (dakle K=RL+1) i ceo podniz se zameni jednim elementom, koji ima vrednost AL or AL+1 or ... or AR1 or AR. Operacija x or y označava operaciju bitovske disjunkcije. Detaljnije informacije možete pročitati u napomeni.

Formalno, označimo sa B niz dobijen posle primene jedne operacije na nizu A sa N elemenata. Niz B ima N(K1) elemenata i to sa sledećim vrednostima:

  • Bi=Ai za 1i<L.
  • BL=AL or AL+1 or ... or AR1 or AR.
  • Bi=Ai+(K1) za L<iN(K1).

Nakon što u nizu ostane manje od K elemenata, posmatramo sumu elemenata koji su preostali. Cilj je da minimizujete sumu elemenata koji preostanu na kraju.

Opisi funkcija

Potrebno je da implementirate funkciju

  • Resi(N,A[],K)

Funkcija Resi se poziva samo jednom na početku ivršavanja programa, a njeni parametri su N, A[] i K. Povratna vrednost funkcije reši je minimalna suma elemenata, nakon primene najvećeg mogućeg broja operacija. Niz A je indeksiran od 1.

Primer

Neka je N=4, A=[2,6,12,5], K=3. Nakon primene operacije na poslednja tri elementa originalnog niza A, dobija se niz [2,6 or 12 or 5]=[2,15] sa sumom 17. Lako se utvrđuje da je ovo najmanja moguća suma, te je potrebno vratiti vrednost 17.

Ograničenja

  • 2KN400.000.
  • 0Ai1.000.000.000.

Podzadaci

Test primeri su podeljeni u 6 podzadatka:

  • [7 poena]: N10.
  • [9 poena]: K3 i N100.000.
  • [15 poena]: Sve vrednosti u nizu A su 1 ili 2 i N100.000.
  • [22 poena]: N3.000.
  • [29 poena]: N100.000.
  • [18 poena]: Nema dodatnih ograničenja

Detalji implementacije

Potrebno je da pošaljete tačno jedan fajl kompresovani_niz.cpp koji implementira pomenute funkcije. Osim traženih funkcija, vaš fajl može sadržati i dodatne globalne promenljive, pomoćne funkcije i dodatne biblioteke.

Vaša funkcija moraju biti sledećeg oblika:

long long Resi(int N, int* A, int K);

Vašim programima je dozvoljeno da menjaju sadržaj nizova ali ne smeju da pristupaju van granica datih nizova.

Uz zadatak, obezbeđen vam je "template" fajl code.cpp koje možete koristiti i menjati po potrebi. Takođe vam je obezbeđen program grader.cpp koji služi da lakše testirate kodove. Ovaj program učitava sa standardnog ulaza sledeće podatke:

  • U prvom redu brojevi N i K.
  • U narednom redu N brojeva: Ai.

Zatim ovaj program zove vašu funkciju i ispisuje rezultate koje ona vrati.

Napomena

Operator disjunkcije je u C++ zapisan pomoću simbola |. Ova operacija x or y se za nenegativne cele brojeve x,y definiše na sledeći način. Prvo se brojevi zapišu u binarnom zapisu. Ukoliko jedan broj ima manje cifara od drugog, dopisuju mu se vodeće nule sve dok ne budu imali isti broj binarnih cifara. Tako se dobijaju dva niza binarnih cifara, označimo ih sa a1,,ak i b1,bk. Zatim se za svaku poziciju i{1,,k} računa ci pomoću sledećih pravila:

  • Za ai=0,bi=0 važi ci=0
  • Za ai=0,bi=1 važi ci=1
  • Za ai=1,bi=0 važi ci=1
  • Za ai=1,bi=1 važi ci=1

Niz binarnih cifara c1,,ck (koji možda ima vodeće nule) je binarni zapis rezultata, odnosno broja x or y.

Bitovska disjunkcija između n elemenata x1,x2,...,xn definiše se kao x1 or x2 or ... or xn=(...(((x1 or x2) or x3) or x4)...) or xn.

Autor Tekst i test primeri Analiza rеšenja Testiranje
Aleksa Milisavljević Aleksa Milisavljević Aleksa Milisavljević Vladimir Milenković

Rešenje kada N10

U ovom podzadatku možemo testirati svaki mogući niz primene operacija. Za dato K imamo NK+1=(N(K1)) opcija za primenu prve operacije. Posle prvog koraka, broj elemenata se smanji za K1, pa u drugom koraku imamo N2(K1) opcija. Ukupno, postoji (N(K1))(N2(K1))...((K1)+(Nmod(K1))) različitih sekvenci operacija koje rezultuju nizom dužine manje od K. Prethodni proizvod je ograničen sa (N1)!, pa je složenost ovog rešenja O(N!).

Rešenje kada K3 i N100.000

Za naredne podzadatke moramo malo detaljnije analizirati kako primene operacija utiču na niz. Za ovaj podzadatak, dovoljno je primetiti da su elementi krajnjeg niza zapravo kompresovani blokovi uzastopnih elemenata u početnom nizu. Ti blokovi uzastopnih elemenata imaju dužinu 1mod(K1), što je lako pokazati. Naime, u početnom nizu blokovi imaju dužinu 1. Svakom daljom kompresijom se K takvih blokova spaja u jedan blok, koji po modulu K1 ima dužinu K1=K=1mod(K1). Konačno, za ovaj podzadatak možemo da analiziramo dva slučaja:

  • K=2 - tada krajnji niz ima dužinu 1, a vrednost tog elementa je or vrednosti svih elemenata u početnom nizu
  • K=3 - tada krajnji niz ima dužinu 1 ukoliko je N neparno i 2, ukoliko je N parno. Ukoliko je N neparno, tada je vrednost jedinog elementa or vrednosti svih elemenata u početnom nizu. Konačno, ukoliko je N parno, možemo da iteriramo po završetku prvog bloka, koji se završava na nekom neparnom indeksu u početnom nizu i da posmatramo prefiksni i sufiksni or elemenata početnog niza. Rešenje zadatka je minimum njihove sume.

Rešenje kada N3.000

Ovaj i naredne podzadatke rešavamo dinamičkim programiranjem. Vrednost dp[i] će predstavljati minimalnu vrednost sume ukoliko se ograničimo isključivo na prvih i elemenata niza. Prvo moramo primetiti da je uslov da se operacije primenjuju sve dok niz ima dovoljno elemenata suštinski nevažan. Naime, primenom operacije se suma elemenata niza ne može povećati, te je svakako uvek bolje primeniti što više operacija. Za svaku poziciju i, iteriramo po završnoj poziciji prethodnog bloka. Rekurentna formula za naše dinamičko programiranje je minj(dp[j]+A[j+1] or A[j+2] or ... or A[i]), gde je ij=1mod(K1) (jer prema opservaciji iz prethodnog podzadatak važi da je dužina svakog bloka 1mod(K1)). Ukoliko održavamo vrednost A[j+1] or A[j+2] or ... or A[i], dok iteriramo po j, dobijamo rešenje složenosti O(N2).

Rešenje kada su sve vrednosti u nizu A su 1 ili 2 i N100.000

I u ovom podzadatku ponovo posmatramo blokove. Svaki blok ima vrednost 1, 2 ili 3. Međutim, blok koji se završava na nekoj poziciji i može da ima samo dve vrednosti, a to su A[i] i 3 (npr. ukoliko i-ti element ima vrednost 1, blok koji se završava na poziciji i nikako ne može da ima vrednost 2 i obrnuto). Za svaku krajnju poziciju, fiksiramo jednu od te dve moguće vrednosti poslednjeg bloka i posmatramo sve moguće startne pozicije trenutnog bloka. Za njih možemo da održavamo minimum koristeći neku strukturu podataka, na primer set.

Rešenje kada N100.000

Rešenje ovog podzadatka predstavlja nadogradnju na rešenje podzadatka u kojem su sve vrednosti u nizu A jednake 1 ili 2. Možemo da primetimo da postoji najviše log2maxiA[i]30 mogućih različitih vrednosti bloka koji se završava na poziciji i za svako i. Sada možemo da fiksiramo jednu od tih vrednosti i pronađemo najraniju poziciju (označimo tu poziciju sa j) na kojoj je trenutni blok mogao da počne da bi imao tu vrednost. Zatim, koristeći neku strukturu, npr. set pronađemo minimum vrednosti dp-ova prethodnog bloka od pozicije j pa do i. Ovo možemo da realizujemo koristeći set na sledeći način. Možemo da čuvamo K1 set-ova, jedan za svaku krajnju pozicija moduo K1. U svakom od njih možemo da čuvamo vrednosti dp-ova sortirano po indeksu. Konačno kada ubacujemo dp[i] u set koji odgovara modulu imod(K1), treba da izbacimo sve one koje koji se pojavljuju pre njega, a imaju veću vrednost od dp[i]. Kada tražimo minimum od pozicije j do pozicije i, možemo da pozovemo ugrađenu funkciju lower_bound, da bi pronašli prvi indeks veći od j koji postoji u odgovarajućem set-u. Složenost ovog rešenja je O(NlogNlogmaxiA[i]).

Glavno rešenje

Za glavno rešenje i poslednji podzadatak, dovoljno je optimizovati složenost na O(NlogmaxiA[i]). Ovo se može postići na primer tako što zamenimo set sa spars tabelom.

02_kompresovani_niz.cpp
#include <bits/stdc++.h>
#define maxn 400005
#define maxl 31
using namespace std;
int l;
long long dp[maxn];
long long prev_bit[maxl];
long long sprs[maxn][21];
int val[maxn];
int bit[2][maxl];
int pos[2][maxl];
pair<int,int> v[2][maxl];
inline long long query(int p,int q) {
    int k=val[q-p];
    return min(sprs[q][k],sprs[p+((1<<k)-1)*l][k]);
}
long long Resi(int N, int* A, int K)
{
    l=K-1;
    int c=0;
    for(int j=1;j*l<maxn;j++) {
        if(2*(1<<c)<=j) c++;
        val[j*l]=c;
    }
    for(int j=0;j<maxl;j++) bit[0][j]=bit[1][j]=j;
    int g=0;
    for(int i=1;i<=N;i++) {
        int c=0;
        for(int j=0;j<maxl;j++) {
            if(A[i]&(1<<bit[g][j])) {
                pos[g^1][c]=i;
                bit[g^1][c]=bit[g][j];
                c++;
            }
        }
        for(int j=0;j<maxl;j++) {
            if(!(A[i]&(1<<bit[g][j]))) {
                pos[g^1][c]=pos[g][j];
                bit[g^1][c]=bit[g][j];
                c++;
            }
        }
        g^=1;
        dp[i]=A[i]+dp[i-1];
        int o=0;
        c=(i-1);
        int fst=c-l*(c/l);
        for(int j=0;j<maxl;j++) {
            if(pos[g][j]==0) break;
            if(pos[g][j]==i) {
                o|=(1<<bit[g][j]);
                continue;
            }
            if(j!=0 && pos[g][j]==pos[g][j-1]) {
                o|=(1<<bit[g][j]);
                continue;
            }
            int rm=c%l;
            int start=c-l*((c-(pos[g][j]))/l);
            dp[i]=min(dp[i],query(start,c)+o);
            o|=(1<<bit[g][j]);
            if(query(fst,start)+o>dp[i]) break;
        }
        int rm=c%l;
        dp[i]=min(dp[i],query(fst,c)+o);
        sprs[i][0]=dp[i];
        for(int j=1;i-((1<<j)-1)*l>=0;j++) {
            sprs[i][j]=min(sprs[i][j-1],sprs[i-((1<<(j-1)))*l][j-1]);
        }
    }
    return dp[N];
}