Generisanje permutacija, kombinacija i varijacija

Lekcija 6 od 7 · Rekurzija i bektreking

Mnogi takmičarski zadaci traže da se isprobaju svi rasporedi ili svi izbori elemenata. Ovde su gotovi šabloni za generisanje permutacija, kombinacija, varijacija i podskupova – rekurzivno i pomoću funkcije next_permutation.

Pre nego što počneš: potrebno predznanje – Bektreking.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u C++-u.

Pojmovi i broj mogućnosti

Šta biramoNazivBrojPrimer za {1, 2, 3}
raspored svih n elemenatapermutacijen!123, 132, 213, 231, 312, 321
k od n, redosled nije bitankombinacijeC(n, k)k = 2: 12, 13, 23
k od n, redosled bitanvarijacijen!/(n−k)!k = 2: 12, 13, 21, 23, 31, 32
k puta po jedan od n (sa ponavljanjem)varijacije sa ponavljanjemnkk = 2: 11, 12, 13, 21, …, 33
bilo koji izborpodskupovi2n{}, {1}, {2}, {3}, {1,2}, …

Kako se ovi brojevi računaju bez generisanja, objašnjeno je u lekciji Kombinatorika.

Permutacije – rekurzivno

Na svaku poziciju redom stavljamo jedan od još neiskorišćenih brojeva. Niz iskoriscen pamti koji su brojevi već u permutaciji.

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

int n;
vector<int> perm;
vector<bool> iskoriscen;

void generisi() {
    if ((int)perm.size() == n) {               // permutacija je kompletna
        for (int x : perm) cout << x << " ";
        cout << "\n";
        return;
    }
    for (int x = 1; x <= n; x++) {
        if (iskoriscen[x]) continue;
        iskoriscen[x] = true; perm.push_back(x);    // izaberi
        generisi();                                  // istraži
        iskoriscen[x] = false; perm.pop_back();     // poništi
    }
}

int main() {
    cin >> n;
    iskoriscen.assign(n + 1, false);
    generisi();
    return 0;
}

Permutacije izlaze u leksikografskom poretku, jer se na svaku poziciju brojevi probaju od najmanjeg.

Kombinacije k od n

Kod kombinacija redosled nije bitan, pa da ne bismo dobili isti izbor dva puta, brojeve biramo rastuće: posle broja x sledeći biramo od x + 1.

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

int n, k;
vector<int> izbor;

void kombinacije(int od) {
    if ((int)izbor.size() == k) {
        for (int x : izbor) cout << x << " ";
        cout << "\n";
        return;
    }
    // odsecanje: ostalo je dovoljno brojeva da se izbor popuni
    for (int x = od; x <= n - (k - (int)izbor.size()) + 1; x++) {
        izbor.push_back(x);
        kombinacije(x + 1);
        izbor.pop_back();
    }
}

int main() {
    cin >> n >> k;
    kombinacije(1);
    return 0;
}

Podskupovi pomoću bitova

Podskup skupa od n elemenata može se predstaviti brojem od 0 do 2n − 1: ako je i-ti bit jedinica, element i je u podskupu. Ovo je kraće od rekurzije i dovoljno brzo za n ≤ 20.

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    for (int maska = 0; maska < (1 << n); maska++) {
        cout << "{ ";
        for (int i = 0; i < n; i++)
            if (maska & (1 << i))                 // da li je i-ti bit jedinica
                cout << i + 1 << " ";
        cout << "}\n";
    }
    return 0;
}

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: Lako

PIN kodovi

PIN ima k cifara (k ≤ 6), a dozvoljene su samo cifre iz date reči c (npr. 137). Ispiši sve moguće PIN-ove u rastućem poretku.

Primer

Ulaz

2 13

Izlaz

11
13
31
33
ZADATAK 02Težina: Srednje teško

Najveći broj deljiv sa 3

Od cifara datog broja (najviše 9 cifara) treba složiti broj koji koristi sve cifre, deljiv je sa 3 i najveći je mogući. Ako takav ne postoji, ispiši -1.

Primer 1

Ulaz

1052

Izlaz

-1

Primer 2

Ulaz

123

Izlaz

321
ZADATAK 03Težina: Srednje teško

Izbor tima

Od n učenika (n ≤ 15) sa datim poenima bira se tim od tačno k učenika. Koliko ima timova čiji je zbir poena bar P?

Primer

Ulaz

4 2 9
5 3 6 4

Izlaz

4
Česte greške i saveti
  • Za next_permutation niz mora biti sortiran na početku – inače se preskaču permutacije.
  • Kombinacije generiši rastuće (od = x + 1), inače dobijaš varijacije sa duplikatima.
  • Pre pisanja izračunaj broj mogućnosti: 10! je 3,6 miliona (u redu), 12! je 479 miliona (presporo za ispis).
  • 1 << n je 2n; za n ≥ 31 koristi 1LL << n.

Proveri znanje

Otvori kviz

Mini kviz: permutacije i kombinacije

1. Koliko ima permutacija skupa od 5 elemenata?




2. Šta vraća next_permutation kada je niz poslednja permutacija?




3. Koji broj predstavlja podskup {1, 3} skupa {1, 2, 3} u zapisu bitovima (bit 0 = element 1)?