Algorytmy na maturę z informatyki
Kurs obejmuje algorytmy z podstawy programowej dla zakresu rozszerzonego. Przy każdym jest pomysł, kod, ślad działania na przykładzie i zadania do sprawdzenia się.
Jak wygląda egzamin
Matura z informatyki (od 2023 r.) to jeden arkusz na 210 minut. Część zadań rozwiązujesz na papierze: śledzisz algorytm, uzupełniasz tabelę, piszesz własny algorytm w pseudokodzie albo w wybranym języku. Większość punktów zdobywasz jednak przy komputerze: dostajesz pliki z danymi, piszesz program i zapisujesz odpowiedzi do pliku wynikowego.
Programy możesz pisać w Pythonie, C++ albo Javie. Kurs pokazuje kod w Pythonie (domyślnie), w C++ i w pseudokodzie, który przydaje się w zadaniach na papierze. Wersję przełączasz u góry strony.
Jak korzystać
- Lekcja zaczyna się od pomysłu. Najpierw go zrozum, potem czytaj kod. Na egzaminie pomysł odtworzysz, a kodu na pamięć nie musisz znać.
- Przepisz kod u siebie i uruchom go na kilku przykładach. Przepisywanie uczy więcej niż kopiowanie.
- Zadania „Policz sam” sprawdzają, czy umiesz wykonać algorytm ręcznie. To dokładnie ten typ zadań, który jest w części papierowej.
- Postęp zapisuje się tylko w tej przeglądarce.
| Grupa | Algorytmy w kursie |
|---|---|
| Liczby | cyfry i dzielniki, budowanie liczb z cyfr, liczby pierwsze, sito Eratostenesa, NWD i rozszerzony Euklides, systemy liczbowe, Horner, zapis binarny i operacje na bitach, szybkie potęgowanie |
| Rekurencja | silnia, Fibonacci, drzewo wywołań, generowanie ciągów, podzbiorów i permutacji |
| Wyszukiwanie | liniowe, min i max, przesunięcie cykliczne, sumy prefiksowe, binarne i szukanie po wyniku |
| Sortowanie | bąbelkowe, przez wybór, przez wstawianie, przez scalanie, szybkie, przez zliczanie, kubełkowe |
| Teksty | palindromy, anagramy, wyszukiwanie wzorca, szyfr Cezara, Vigenère’a, przestawieniowy |
| Struktury | plansze 2D i BFS, drzewo BST w tablicy, kopiec, hierarchia z tablicą przełożonych |
| Numeryczne | miejsce zerowe metodą połowienia, pierwiastek metodą Newtona, pole pod wykresem, Monte Carlo, geometria na liczbach całkowitych |
| Strategie | dziel i zwyciężaj, algorytm zachłanny (reszta, przedziały), programowanie dynamiczne |
| Arkusz i bazy | formuły, daty, tabela przestawna, wykres, symulacja dzień po dniu; SQL: grupowanie, łączenie tabel, LEFT JOIN, podzapytania |
Tematy dobrane są według arkuszy rozszerzonych z lat 2015–2026. Przy lekcjach są notatki „Na maturze”, a zestawienie arkuszy pokazuje, które zadanie ćwiczy dana lekcja.
Wczytywanie danych i zapis wyników
Każde zadanie przy komputerze zaczyna się tak samo: wczytaj plik, policz, zapisz odpowiedź. Ten szkielet warto umieć napisać bez zastanowienia.
Pliki z danymi leżą w folderze podanym w arkuszu. Program uruchamiaj w tym samym folderze, wtedy wystarczy sama nazwa pliku. Odpowiedzi zapisujesz do pliku wynikowego, którego nazwę podaje treść zadania. Każdą odpowiedź poprzedź numerem zadania.
# wszystkie liczby z pliku, niezależnie od podziału na wiersze
with open("dane.txt") as f:
liczby = [int(x) for x in f.read().split()]
# plik czytany wiersz po wierszu, w każdym wierszu dwie liczby
pary = []
with open("pary.txt") as f:
for wiersz in f:
a, b = map(int, wiersz.split())
pary.append((a, b))
parzyste = sum(1 for x in liczby if x % 2 == 0)
with open("wyniki.txt", "w") as out:
print("1.1.", parzyste, file=out)#include <fstream>
#include <vector>
using namespace std;
int main() {
ifstream we("dane.txt");
vector<int> liczby;
int x;
while (we >> x) // czyta, dopóki są liczby
liczby.push_back(x);
int parzyste = 0;
for (int v : liczby)
if (v % 2 == 0) parzyste++;
ofstream wy("wyniki.txt");
wy << "1.1. " << parzyste << "\n";
}wczytaj wszystkie liczby z pliku dane.txt do tablicy T[0..n-1]
parzyste ← 0
dla i = 0, 1, …, n-1 wykonuj
jeżeli T[i] mod 2 = 0
parzyste ← parzyste + 1
zapisz do pliku wyniki.txt: "1.1.", parzystePrzykładowy plik dane.txt i wynik programu:
12 7 30
5 8
411.1. 3Wiersze z tekstem
Gdy w wierszu jest słowo, pamiętaj o strip(), bo wiersz kończy się znakiem nowej linii. Metoda split() bez argumentu sama pomija spacje i znaki nowej linii.
Operator >> sam pomija spacje i znaki nowej linii, więc we >> slowo wczyta kolejne słowo. Cały wiersz ze spacjami wczytasz funkcją getline(we, wiersz).
W Pythonie wczytujesz słowa pętlą for w in f: i sprawdzasz w == w[::-1]. Żadne słowo nie wychodzi palindromem. Dlaczego?
- Python źle odwraca napisy
- Każde
wkończy się znakiem\n, który po odwróceniu jest na początku - Plik trzeba otworzyć w trybie
"rb"
Złożoność obliczeniowa
Złożoność mówi, jak rośnie liczba operacji, gdy rosną dane. Na maturze pytają o nią wprost i pośrednio: program, który działa minutę zamiast sekundy, nie zdąży z dużym plikiem.
Liczymy operacje dominujące, np. porównania albo mnożenia, i patrzymy tylko na najszybciej rosnący składnik. Algorytm, który wykonuje 3n² + 5n + 2 porównań, ma złożoność O(n²).
| Złożoność | Nazwa | Przykład | n = 1 000 000 |
|---|---|---|---|
| O(1) | stała | dostęp do t[i] | 1 |
| O(log n) | logarytmiczna | wyszukiwanie binarne, szybkie potęgowanie | ≈ 20 |
| O(√n) | pierwiastkowa | test pierwszości przez dzielenie | 1 000 |
| O(n) | liniowa | minimum, wyszukiwanie liniowe | 1 000 000 |
| O(n log n) | liniowo-logarytmiczna | sortowanie przez scalanie | ≈ 20 000 000 |
| O(n²) | kwadratowa | sortowanie bąbelkowe | 10¹² |
| O(2ⁿ) | wykładnicza | Fibonacci rekurencyjnie bez pamięci | nie do policzenia |
Zgrubna miara: zwykły komputer wykonuje około 10⁸ prostych operacji na sekundę w C++ i kilka razy mniej w Pythonie. Pliki maturalne mają zwykle od kilkuset do kilkudziesięciu tysięcy wierszy, więc O(n²) jeszcze zwykle przejdzie, ale O(2ⁿ) już nie.
Pętle w pętlach
Dwie zagnieżdżone pętle po n elementach dają O(n²). Pętla, w której zmienna jest co krok dzielona przez 2, wykona się około log₂ n razy.
Jaka jest złożoność pętli i = n; while i > 1: i = i // 2?
- O(n)
- O(log n)
- O(n / 2)
- O(√n)
Algorytm wykonuje n²/2 + 100n porównań. Jaka jest jego złożoność?
- O(n)
- O(n²)
- O(100n)
- O(n³)
Ile razy (w przybliżeniu, jako liczbę całkowitą) wykona się pętla połowiąca dla n = 1024?
Cyfry liczby i jej dzielniki
Dwie operacje wystarczą do rozebrania liczby na cyfry: reszta z dzielenia przez 10 daje ostatnią cyfrę, a dzielenie całkowite przez 10 ją obcina.
def suma_cyfr(n):
s = 0
while n > 0:
s += n % 10 # ostatnia cyfra
n //= 10 # obcinamy ostatnią cyfrę
return s
def odwroc(n):
w = 0
while n > 0:
w = w * 10 + n % 10
n //= 10
return wint suma_cyfr(long long n) {
int s = 0;
while (n > 0) {
s += n % 10; // ostatnia cyfra
n /= 10; // obcinamy ostatnią cyfrę
}
return s;
}
long long odwroc(long long n) {
long long w = 0;
while (n > 0) {
w = w * 10 + n % 10;
n /= 10;
}
return w;
}funkcja suma_cyfr(n)
s ← 0
dopóki n > 0 wykonuj
s ← s + (n mod 10) // ostatnia cyfra
n ← n div 10 // obcinamy ostatnią cyfrę
zwróć s
funkcja odwroc(n)
w ← 0
dopóki n > 0 wykonuj
w ← w · 10 + (n mod 10)
n ← n div 10
zwróć w| n | n % 10 | s po kroku |
|---|---|---|
| 2024 | 4 | 4 |
| 202 | 2 | 6 |
| 20 | 0 | 6 |
| 2 | 2 | 8 |
| 0 | — | koniec |
Dzielniki do pierwiastka
Dzielniki występują w parach: jeśli d dzieli n, to n / d też. Mniejszy z pary jest zawsze ≤ √n. Wystarczy więc sprawdzać d od 1 do √n i dopisywać oba dzielniki z pary. To O(√n) zamiast O(n).
def dzielniki(n):
wynik = []
d = 1
while d * d <= n:
if n % d == 0:
wynik.append(d)
if d != n // d: # para d i n/d, bez dublowania pierwiastka
wynik.append(n // d)
d += 1
return sorted(wynik)
def doskonala(n):
return sum(dzielniki(n)) - n == n#include <vector>
#include <algorithm>
using namespace std;
vector<long long> dzielniki(long long n) {
vector<long long> wynik;
for (long long d = 1; d * d <= n; d++) {
if (n % d == 0) {
wynik.push_back(d);
if (d != n / d) // para d i n/d, bez dublowania pierwiastka
wynik.push_back(n / d);
}
}
sort(wynik.begin(), wynik.end());
return wynik;
}
bool doskonala(long long n) {
long long s = 0;
for (long long d : dzielniki(n)) s += d;
return s - n == n;
}funkcja dzielniki(n)
D ← pusta lista
d ← 1
dopóki d · d ≤ n wykonuj
jeżeli n mod d = 0
dopisz d do D
jeżeli d ≠ n div d
dopisz n div d do D // para d i n/d
d ← d + 1
zwróć D posortowaną rosnąco
funkcja doskonala(n)
zwróć (suma elementów dzielniki(n)) − n = nd * d <= n zamiast d <= sqrt(n) chroni przed błędem zaokrąglenia liczb zmiennoprzecinkowych.Liczba doskonała to taka, która jest równa sumie swoich dzielników mniejszych od niej, np. 6 = 1 + 2 + 3 i 28 = 1 + 2 + 4 + 7 + 14.
Jaka jest suma cyfr liczby 2024?
Ile dzielników ma liczba 36?
Ile razy wykona się pętla w funkcji dzielniki dla n = 10 000?
- 10 000
- 100
- 5 000
- 50
Budowanie nowej liczby z cyfr
Częste zadanie maturalne: z cyfr jednej liczby zbudować drugą, np. bez cyfr parzystych albo z każdą cyfrą zamienioną na inną. Wystarczą dzielenie przez 10 i zmienna, która pamięta pozycję następnej cyfry.
Cyfry odczytujemy od końca (n % 10). Żeby nowa liczba miała je w tej samej kolejności, każdą kolejną cyfrę mnożymy przez 1, 10, 100… Ten mnożnik rośnie tylko wtedy, gdy cyfra faktycznie trafia do wyniku.
def tylko_nieparzyste(n): # 52318 -> 531: zostawia cyfry nieparzyste
wynik = 0
mnoznik = 1 # pozycja, na którą trafi następna cyfra
while n > 0:
c = n % 10
if c % 2 == 1:
wynik += c * mnoznik
mnoznik *= 10
n //= 10
return wynik
def dopelnienie(n): # każda cyfra c zamienia się na 9 - c
wynik = 0
mnoznik = 1
while True:
wynik += (9 - n % 10) * mnoznik
mnoznik *= 10
n //= 10
if n == 0:
return wynik
def przeniesienia(a, b): # ile razy przy dodawaniu pisemnym jest przeniesienie
ile = 0
p = 0
while a > 0 or b > 0:
p = (a % 10 + b % 10 + p) // 10 # 1, gdy suma cyfr z przeniesieniem ≥ 10
ile += p
a //= 10
b //= 10
return ilelong long tylko_nieparzyste(long long n) { // 52318 -> 531: zostawia cyfry nieparzyste
long long wynik = 0, mnoznik = 1; // mnoznik: pozycja następnej cyfry
while (n > 0) {
int c = n % 10;
if (c % 2 == 1) {
wynik += c * mnoznik;
mnoznik *= 10;
}
n /= 10;
}
return wynik;
}
long long dopelnienie(long long n) { // każda cyfra c zamienia się na 9 - c
long long wynik = 0, mnoznik = 1;
do {
wynik += (9 - n % 10) * mnoznik;
mnoznik *= 10;
n /= 10;
} while (n > 0);
return wynik;
}
int przeniesienia(long long a, long long b) { // przeniesienia przy dodawaniu pisemnym
int ile = 0, p = 0;
while (a > 0 || b > 0) {
p = (a % 10 + b % 10 + p) / 10; // 1, gdy suma cyfr z przeniesieniem >= 10
ile += p;
a /= 10;
b /= 10;
}
return ile;
}funkcja tylko_nieparzyste(n) // 52318 -> 531
w ← 0; mnoznik ← 1 // mnoznik: pozycja następnej cyfry
dopóki n > 0 wykonuj
c ← n mod 10
jeżeli c mod 2 = 1
w ← w + c · mnoznik
mnoznik ← mnoznik · 10
n ← n div 10
zwróć w
funkcja dopelnienie(n) // każda cyfra c → 9 − c
w ← 0; mnoznik ← 1
powtarzaj
w ← w + (9 − n mod 10) · mnoznik
mnoznik ← mnoznik · 10
n ← n div 10
aż n = 0
zwróć w
funkcja przeniesienia(a, b)
ile ← 0; p ← 0
dopóki a > 0 lub b > 0 wykonuj
p ← (a mod 10 + b mod 10 + p) div 10
ile ← ile + p
a ← a div 10; b ← b div 10
zwróć ile| n | cyfra | nieparzysta? | wynik | mnoznik po kroku |
|---|---|---|---|---|
| 52318 | 8 | nie | 0 | 1 |
| 5231 | 1 | tak | 1 | 10 |
| 523 | 3 | tak | 31 | 100 |
| 52 | 2 | nie | 31 | 100 |
| 5 | 5 | tak | 531 | 1000 |
Dodawanie pisemne działa tak samo: idziemy od ostatnich cyfr, a przeniesienie to (cyfra_a + cyfra_b + poprzednie) div 10. Pętla kręci się, dopóki którakolwiek liczba ma jeszcze cyfry.
div i mod, tak jak powyżej. Gdy takiego zakazu nie ma, w programie możesz pracować na napisie str(n).Co zwróci tylko_nieparzyste(70315)?
Jakie jest dopełnienie liczby 3051?
Ile przeniesień jest przy dodawaniu pisemnym 4567 + 5433?
Liczby pierwsze i rozkład na czynniki
Liczba pierwsza ma dokładnie dwa dzielniki: 1 i samą siebie. Wystarczy sprawdzić, czy nie dzieli jej żadna liczba od 2 do √n.
def czy_pierwsza(n):
if n < 2:
return False
d = 2
while d * d <= n:
if n % d == 0:
return False
d += 1
return True
def czynniki(n):
wynik = []
d = 2
while d * d <= n:
while n % d == 0:
wynik.append(d)
n //= d
d += 1
if n > 1: # została jedna duża liczba pierwsza
wynik.append(n)
return wynik#include <vector>
using namespace std;
bool czy_pierwsza(long long n) {
if (n < 2) return false;
for (long long d = 2; d * d <= n; d++)
if (n % d == 0) return false;
return true;
}
vector<long long> czynniki(long long n) {
vector<long long> wynik;
for (long long d = 2; d * d <= n; d++)
while (n % d == 0) {
wynik.push_back(d);
n /= d;
}
if (n > 1) // została jedna duża liczba pierwsza
wynik.push_back(n);
return wynik;
}funkcja czy_pierwsza(n)
jeżeli n < 2
zwróć FAŁSZ
d ← 2
dopóki d · d ≤ n wykonuj
jeżeli n mod d = 0
zwróć FAŁSZ
d ← d + 1
zwróć PRAWDA
funkcja czynniki(n)
C ← pusta lista
d ← 2
dopóki d · d ≤ n wykonuj
dopóki n mod d = 0 wykonuj
dopisz d do C
n ← n div d
d ← d + 1
jeżeli n > 1
dopisz n do C // została jedna duża liczba pierwsza
zwróć CW rozkładzie na czynniki dzielimy przez d, dopóki się da, dopiero potem zwiększamy d. Dzięki temu do listy trafiają tylko liczby pierwsze: zanim dojdziemy do 4, wszystkie dwójki już zostały wyciągnięte.
| d | n przed | dopisane czynniki | n po |
|---|---|---|---|
| 2 | 360 | 2, 2, 2 | 45 |
| 3 | 45 | 3, 3 | 5 |
| 4 | 5 | — (4·4 > 5, koniec pętli) | 5 |
| n > 1, więc 5 |
n < 2 funkcja uzna je za pierwsze, bo pętla nie wykona się ani razu.Podaj rozkład liczby 360 na czynniki pierwsze, od najmniejszego, oddzielając spacjami.
Dlaczego w teście pierwszości wystarczy sprawdzać dzielniki do √n?
- Bo liczby pierwsze są mniejsze od √n
- Bo jeśli n = a·b i oba czynniki są większe od √n, to a·b > n; jeden z nich musi być ≤ √n
- Bo tak jest szybciej, choć czasem daje błędny wynik
Czy 91 jest liczbą pierwszą?
- Tak
- Nie, 91 = 7 · 13
Sito Eratostenesa
Gdy potrzebujesz wszystkich liczb pierwszych do n albo musisz sprawdzić tysiące liczb, sito jest dużo szybsze niż osobny test dla każdej z nich.
Zaczynamy od tablicy „wszystko pierwsze”. Bierzemy najmniejszą niewykreśloną liczbę i wykreślamy wszystkie jej wielokrotności. Powtarzamy aż do √n. Co zostało, jest pierwsze.
def sito(n):
pierwsza = [True] * (n + 1)
pierwsza[0] = pierwsza[1] = False
i = 2
while i * i <= n:
if pierwsza[i]:
for j in range(i * i, n + 1, i): # wielokrotności i
pierwsza[j] = False
i += 1
return [k for k in range(n + 1) if pierwsza[k]]#include <vector>
using namespace std;
vector<int> sito(int n) {
vector<bool> pierwsza(n + 1, true);
pierwsza[0] = pierwsza[1] = false;
for (int i = 2; i * i <= n; i++)
if (pierwsza[i])
for (int j = i * i; j <= n; j += i) // wielokrotności i
pierwsza[j] = false;
vector<int> wynik;
for (int k = 2; k <= n; k++)
if (pierwsza[k]) wynik.push_back(k);
return wynik;
}funkcja sito(n)
P[0..n] ← PRAWDA
P[0] ← FAŁSZ; P[1] ← FAŁSZ
i ← 2
dopóki i · i ≤ n wykonuj
jeżeli P[i]
j ← i · i
dopóki j ≤ n wykonuj // wielokrotności i
P[j] ← FAŁSZ
j ← j + i
i ← i + 1
zwróć wszystkie k, dla których P[k] = PRAWDAWykreślanie zaczynamy od i·i, bo mniejsze wielokrotności, np. 2·i albo 3·i, wykreśliły już wcześniejsze liczby. Złożoność to O(n log log n), w praktyce prawie liniowa.
2 3 5 7 11 13 17 19 23 29pierwsza[x].Ile jest liczb pierwszych mniejszych lub równych 30?
Od której liczby zaczyna się wykreślanie wielokrotności 7?
- 14
- 21
- 49
- 7
Algorytm Euklidesa: NWD i NWW
Największy wspólny dzielnik dwóch liczb nie zmienia się, gdy od większej odejmiemy mniejszą. Na tej obserwacji opiera się jeden z najstarszych algorytmów.
Wersja z odejmowaniem jest prosta, ale wolna dla liczb bardzo różnej wielkości, np. NWD(1000000, 1) wymaga miliona kroków. Wersja z resztą z dzielenia robi wiele odejmowań naraz i wykonuje O(log n) kroków.
def nwd_odejmowanie(a, b):
while a != b:
if a > b:
a -= b
else:
b -= a
return a
def nwd(a, b):
while b != 0:
a, b = b, a % b
return a
def nwd_rek(a, b):
if b == 0:
return a
return nwd_rek(b, a % b)
def nww(a, b):
return a // nwd(a, b) * b # najpierw dzielenie: mniejsze liczby po drodzelong long nwd_odejmowanie(long long a, long long b) {
while (a != b) {
if (a > b) a -= b;
else b -= a;
}
return a;
}
long long nwd(long long a, long long b) {
while (b != 0) {
long long r = a % b;
a = b;
b = r;
}
return a;
}
long long nwd_rek(long long a, long long b) {
if (b == 0) return a;
return nwd_rek(b, a % b);
}
long long nww(long long a, long long b) {
return a / nwd(a, b) * b; // najpierw dzielenie: chroni przed przepełnieniem
}funkcja nwd_odejmowanie(a, b)
dopóki a ≠ b wykonuj
jeżeli a > b
a ← a − b
w przeciwnym razie
b ← b − a
zwróć a
funkcja nwd(a, b)
dopóki b ≠ 0 wykonuj
r ← a mod b
a ← b
b ← r
zwróć a
funkcja nwd_rek(a, b)
jeżeli b = 0
zwróć a
zwróć nwd_rek(b, a mod b)
funkcja nww(a, b)
zwróć (a div nwd(a, b)) · b| a | b | a % b |
|---|---|---|
| 48 | 18 | 12 |
| 18 | 12 | 6 |
| 12 | 6 | 0 |
| 6 | 0 | koniec: NWD = 6 |
NWW liczymy ze wzoru NWW(a, b) = a · b / NWD(a, b).
Python liczy na dowolnie dużych liczbach całkowitych, ale a // nwd(a, b) * b i tak jest lepsze, bo liczby po drodze są mniejsze. Używaj //, a nie /, bo / daje liczbę zmiennoprzecinkową.
Kolejność działań ma znaczenie: a / nwd(a, b) * b nie przekroczy zakresu long long tam, gdzie a * b już by go przekroczyło.
NWD wielu liczb liczysz po kolei: NWD(a, b, c) = NWD(NWD(a, b), c). Tak samo NWW.
Ile wynosi NWD(48, 18)?
Ile wynosi NWW(12, 18)?
Rozszerzony algorytm Euklidesa
Wersja rozszerzona oprócz d = NWD(a, b) znajduje liczby całkowite x i y, dla których a·x + b·y = d. Wywołanie dla (b, a mod b) zwraca x′ i y′, a z nich: x = y′ oraz y = x′ − (a div b)·y′.
def nwd_rozszerzony(a, b): # zwraca (d, x, y), gdzie d = NWD(a, b) = a·x + b·y
if b == 0:
return a, 1, 0
d, x, y = nwd_rozszerzony(b, a % b)
return d, y, x - (a // b) * y// zwraca d = NWD(a, b) i ustawia x, y tak, że d = a·x + b·y
long long nwd_rozszerzony(long long a, long long b, long long& x, long long& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
long long x1, y1;
long long d = nwd_rozszerzony(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return d;
}// zwraca (d, x, y), gdzie d = NWD(a, b) = a·x + b·y
funkcja nwd_rozszerzony(a, b)
jeżeli b = 0
zwróć (a, 1, 0)
(d, x, y) ← nwd_rozszerzony(b, a mod b)
zwróć (d, y, x − (a div b) · y)Rozszerzony Euklides dla a = 240, b = 46 daje d = 2 i x = −9. Ile wynosi y?
Ile kroków pętli (ile reszt z dzielenia) wykona funkcja nwd(21, 13)?
Systemy liczbowe i schemat Hornera
Zamiana na system o podstawie p to powtarzane dzielenie przez p. Reszty, czytane od ostatniej, to kolejne cyfry.
CYFRY = "0123456789ABCDEF"
def na_system(n, p):
if n == 0:
return "0"
wynik = ""
while n > 0:
wynik = CYFRY[n % p] + wynik # reszty czytamy od końca
n //= p
return wynik
def z_systemu(s, p): # schemat Hornera
wynik = 0
for znak in s:
wynik = wynik * p + CYFRY.index(znak)
return wynik#include <string>
using namespace std;
const string CYFRY = "0123456789ABCDEF";
string na_system(long long n, int p) {
if (n == 0) return "0";
string wynik = "";
while (n > 0) {
wynik = CYFRY[n % p] + wynik; // reszty czytamy od końca
n /= p;
}
return wynik;
}
long long z_systemu(const string& s, int p) { // schemat Hornera
long long wynik = 0;
for (char znak : s)
wynik = wynik * p + (long long)CYFRY.find(znak);
return wynik;
}CYFRY ← "0123456789ABCDEF"
funkcja na_system(n, p)
jeżeli n = 0
zwróć "0"
w ← ""
dopóki n > 0 wykonuj
w ← CYFRY[n mod p] + w // reszty czytamy od końca
n ← n div p
zwróć w
funkcja z_systemu(s, p) // schemat Hornera
w ← 0
dla każdego znaku z w s, od lewej, wykonuj
w ← w · p + (wartość cyfry z)
zwróć w| n | n % 2 | n // 2 |
|---|---|---|
| 45 | 1 | 22 |
| 22 | 0 | 11 |
| 11 | 1 | 5 |
| 5 | 1 | 2 |
| 2 | 0 | 1 |
| 1 | 1 | 0 |
Reszty od dołu: 101101. W drugą stronę działa schemat Hornera: zaczynamy od zera i dla każdej cyfry od lewej mnożymy wynik przez p i dodajemy cyfrę. 1F₁₆ = 1·16 + 15 = 31.
int("1F", 16) zamienia na liczbę dziesiętną, a bin(45), oct(45), hex(45) w drugą stronę (z przedrostkiem 0b, 0o, 0x). Na egzaminie możesz z nich korzystać, ale zadanie na papierze wymaga znajomości algorytmu.stoll("1F", nullptr, 16) zamienia napis w systemie o podstawie 16 na liczbę. W drugą stronę nie ma gotowej funkcji dla dowolnej podstawy, więc na_system warto umieć napisać.Horner dla wielomianu
Ten sam schemat liczy wartość wielomianu z n mnożeniami zamiast z n² mnożeniami: 2x³ − 3x² + x − 5 = ((2x − 3)x + 1)x − 5.
def horner(wsp, x):
# wsp: współczynniki od najwyższej potęgi, np. 2x³ - 3x² + x - 5 -> [2, -3, 1, -5]
w = 0
for a in wsp:
w = w * x + a
return w#include <vector>
using namespace std;
// wsp: współczynniki od najwyższej potęgi, np. 2x³ - 3x² + x - 5 -> {2, -3, 1, -5}
long long horner(const vector<long long>& wsp, long long x) {
long long w = 0;
for (long long a : wsp)
w = w * x + a;
return w;
}// a[0..n] – współczynniki od najwyższej potęgi
funkcja horner(a, x)
w ← 0
dla i = 0, 1, …, n wykonuj
w ← w · x + a[i]
zwróć wZapisz liczbę 45 w systemie dwójkowym.
Ile wynosi liczba 1F zapisana w systemie szesnastkowym, w zapisie dziesiętnym?
Oblicz schematem Hornera wartość 2x³ − 3x² + x − 5 dla x = 2.
Cyfry zapisane symbolami
Zadanie może podać liczbę w nietypowym zapisie, np. symbolami o, +, * zamiast cyfr 0, 1, 2 systemu trójkowego. Wtedy wystarczy podmienić symbole na cyfry i zastosować schemat Hornera: CYFRY = "o+*" w funkcji z_systemu.
W Pythonie podmianę zrobisz jednym wywołaniem: int(s.translate(str.maketrans("o+*", "012")), 3).
W C++ wystarczy CYFRY.find(znak) z napisem "o+*".
o, +, * były liczbami w systemie trójkowym. Trzeba było znaleźć największą i policzyć sumę.Symbole o, +, * oznaczają cyfry 0, 1, 2 systemu trójkowego. Ile dziesiętnie wynosi +*o?
Ile cyfr ma liczba 255 w systemie dwójkowym?
- 7
- 8
- 9
Zapis binarny i operacje na bitach
Zapis dwójkowy liczby czytamy tak jak dziesiętny, tylko dzielimy przez 2 zamiast przez 10. Do tego dochodzą operatory działające na wszystkich bitach naraz.
| Operator | Python i C++ | Przykład (12 = 1100₂, 10 = 1010₂) |
|---|---|---|
| AND (oba bity 1) | a & b | 12 & 10 = 8 (1000₂) |
| OR (co najmniej jeden 1) | a | b | 12 | 10 = 14 (1110₂) |
| XOR (dokładnie jeden 1) | a ^ b | 12 ^ 10 = 6 (0110₂) |
| przesunięcie w prawo | a >> k | 12 >> 1 = 6, to samo co 12 div 2 |
| przesunięcie w lewo | a << k | 1 << 4 = 16, czyli 2⁴ |
Blok to najdłuższy ciąg takich samych cyfr obok siebie, np. 110011100 ma bloki 11, 00, 111, 00. Przeglądając bity od końca, liczymy, ile razy cyfra różni się od poprzedniej.
def bloki(n): # bloki takich samych cyfr w zapisie binarnym n > 0
ile = 1
ostatni = n % 2
n //= 2
while n > 0:
if n % 2 != ostatni: # zmiana cyfry: zaczyna się nowy blok
ile += 1
ostatni = n % 2
n //= 2
return ile
def jedynki(n): # liczba jedynek w zapisie binarnym
ile = 0
while n > 0:
ile += n & 1 # ostatni bit
n >>= 1 # przesunięcie w prawo, czyli n // 2
return ile
def potega_dwojki(n): # n > 0 ma dokładnie jedną jedynkę
return n & (n - 1) == 0
def gray(n): # kod Graya: sąsiednie liczby różnią się jednym bitem
return n ^ (n >> 1)int bloki(long long n) { // bloki takich samych cyfr w zapisie binarnym n > 0
int ile = 1, ostatni = n % 2;
n /= 2;
while (n > 0) {
if (n % 2 != ostatni) { // zmiana cyfry: zaczyna się nowy blok
ile++;
ostatni = n % 2;
}
n /= 2;
}
return ile;
}
int jedynki(long long n) { // liczba jedynek w zapisie binarnym
int ile = 0;
while (n > 0) {
ile += n & 1; // ostatni bit
n >>= 1; // przesunięcie w prawo, czyli n / 2
}
return ile;
}
bool potega_dwojki(long long n) { // n > 0 ma dokładnie jedną jedynkę
return (n & (n - 1)) == 0; // nawiasy obowiązkowe: == wiąże mocniej niż &
}
long long gray(long long n) { // kod Graya: sąsiednie liczby różnią się jednym bitem
return n ^ (n >> 1);
}funkcja bloki(n) // n > 0
ile ← 1
ostatni ← n mod 2
n ← n div 2
dopóki n > 0 wykonuj
jeżeli n mod 2 ≠ ostatni // nowy blok
ile ← ile + 1
ostatni ← n mod 2
n ← n div 2
zwróć ile
funkcja jedynki(n)
ile ← 0
dopóki n > 0 wykonuj
ile ← ile + (n mod 2)
n ← n div 2
zwróć ile
funkcja potega_dwojki(n) // n > 0
zwróć (n AND (n − 1)) = 0
funkcja gray(n)
zwróć n XOR (n div 2)n XOR (n div 2) to kod Graya: kolejne liczby różnią się w nim dokładnie jednym bitem. Z kolei n AND (n − 1) usuwa najmłodszą jedynkę, więc daje 0 tylko dla potęg dwójki.
Liczbę zapisaną binarnie w pliku zamienisz poleceniem int(s, 2), a z powrotem poleceniem bin(n)[2:] albo format(n, "b").
Liczbę zapisaną binarnie w pliku zamienisz poleceniem stoll(s, nullptr, 2), a z powrotem funkcją na_system(n, 2) z lekcji o systemach. Uwaga: w C++ == wiąże mocniej niż &, więc pisz (n & 1) == 0.
p XOR (p div 2). W 2017 r. (zad. 2) funkcja rekurencyjna przechodziła po bitach liczby, a w 2020 r. w teście (zad. 3.1) pojawił się zegar binarny.Ile bloków ma zapis binarny 110011100?
Ile wynosi 12 XOR 10 (dziesiętnie)?
Ile wynosi gray(11), czyli 1011₂ XOR 0101₂? Podaj wynik binarnie.
Jak sprawdzić, czy liczba n > 0 jest potęgą dwójki?
n % 2 == 0n & (n − 1) == 0n ^ 1 == 0
Szybkie potęgowanie
Zamiast mnożyć a przez siebie n razy, wykorzystujemy aⁿ = (aⁿᐟ²)². Liczba mnożeń spada z n do około 2·log₂ n.
Dla parzystego n liczymy aⁿᐟ² raz i podnosimy do kwadratu. Dla nieparzystego zapisujemy aⁿ = a · aⁿ⁻¹. To przykład metody dziel i zwyciężaj.
def potega(a, n):
if n == 0:
return 1
if n % 2 == 1:
return a * potega(a, n - 1)
p = potega(a, n // 2)
return p * p # jedno wywołanie, wynik użyty dwa razy
def potega_mod(a, n, m):
wynik = 1
a %= m
while n > 0:
if n % 2 == 1: # bieżący bit wykładnika to 1
wynik = wynik * a % m
a = a * a % m
n //= 2
return wyniklong long potega(long long a, int n) {
if (n == 0) return 1;
if (n % 2 == 1) return a * potega(a, n - 1);
long long p = potega(a, n / 2);
return p * p; // jedno wywołanie, wynik użyty dwa razy
}
long long potega_mod(long long a, long long n, long long m) {
long long wynik = 1;
a %= m;
while (n > 0) {
if (n % 2 == 1) // bieżący bit wykładnika to 1
wynik = wynik * a % m;
a = a * a % m;
n /= 2;
}
return wynik;
}funkcja potega(a, n)
jeżeli n = 0
zwróć 1
jeżeli n mod 2 = 1
zwróć a · potega(a, n − 1)
p ← potega(a, n div 2)
zwróć p · p // jedno wywołanie, wynik użyty dwa razy
funkcja potega_mod(a, n, m)
w ← 1
a ← a mod m
dopóki n > 0 wykonuj
jeżeli n mod 2 = 1 // bieżący bit wykładnika to 1
w ← (w · a) mod m
a ← (a · a) mod m
n ← n div 2
zwróć wWersja iteracyjna przegląda bity wykładnika od najmłodszego: 13 = 1101₂, więc a¹³ = a⁸ · a⁴ · a¹. Liczenie reszty z dzielenia po każdym mnożeniu trzyma liczby w małym zakresie, więc obliczenia są szybkie nawet dla wykładnika rzędu 10¹⁸.
Python ma tę funkcję wbudowaną: pow(a, n, m). Liczby całkowite w Pythonie nie mają limitu, ale bez reszty po drodze stają się ogromne i obliczenia zwalniają.
W C++ to konieczność: long long mieści tylko około 9·10¹⁸, więc dla m do 10⁹ iloczyn dwóch reszt jeszcze się mieści.
return potega(a, n // 2) * potega(a, n // 2) wywołuje funkcję dwa razy i znowu daje O(n). Zapisz wynik w zmiennej.Ile wynosi 3¹³ mod 7?
Ile wynosi 2¹⁰?
Jaka jest złożoność szybkiego potęgowania względem wykładnika n?
- O(n)
- O(log n)
- O(√n)
- O(n²)
Rekurencja i ciąg Fibonacciego
Funkcja rekurencyjna wywołuje samą siebie dla mniejszego problemu. Potrzebuje warunku zakończenia, inaczej wywołania nigdy się nie skończą.
def silnia(n):
if n <= 1: # warunek zakończenia
return 1
return n * silnia(n - 1)
def fib_rek(n): # proste, ale wykładniczo wolne
if n < 2:
return n
return fib_rek(n - 1) + fib_rek(n - 2)
def fib(n): # iteracyjnie: O(n)
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return along long silnia(int n) {
if (n <= 1) return 1; // warunek zakończenia
return n * silnia(n - 1);
}
long long fib_rek(int n) { // proste, ale wykładniczo wolne
if (n < 2) return n;
return fib_rek(n - 1) + fib_rek(n - 2);
}
long long fib(int n) { // iteracyjnie: O(n)
long long a = 0, b = 1;
for (int i = 0; i < n; i++) {
long long c = a + b;
a = b;
b = c;
}
return a;
}funkcja silnia(n)
jeżeli n ≤ 1
zwróć 1 // warunek zakończenia
zwróć n · silnia(n − 1)
funkcja fib_rek(n) // wykładniczo wolne
jeżeli n < 2
zwróć n
zwróć fib_rek(n − 1) + fib_rek(n − 2)
funkcja fib(n) // iteracyjnie: O(n)
a ← 0; b ← 1
powtórz n razy
c ← a + b
a ← b
b ← c
zwróć aRekurencyjny Fibonacci liczy te same wartości wiele razy: fib_rek(5) woła fib_rek(3) dwa razy, a fib_rek(2) trzy razy. Liczba wywołań rośnie wykładniczo. Wersja iteracyjna pamięta tylko dwie ostatnie wartości i ma O(n).
Rekurencję można przyspieszyć, zapamiętując obliczone wartości w tablicy (spamiętywanie). To pierwszy krok do programowania dynamicznego.
Ile wynosi fib(10), jeśli fib(0) = 0 i fib(1) = 1?
Ile razy łącznie zostanie wywołana funkcja fib_rek przy obliczaniu fib_rek(5), licząc pierwsze wywołanie?
- 5
- 8
- 15
- 32
Ile wynosi silnia(6)?
Generowanie wszystkich ciągów, podzbiorów i permutacji
Gdy trzeba sprawdzić wszystkie możliwości, rekurencja dokłada po jednym elemencie i schodzi głębiej. Wywołania tworzą drzewo, a liście drzewa to gotowe rozwiązania.
def ciagi(s, n, k, wynik): # wszystkie napisy długości n z cyfr 0..k-1
if len(s) == n:
wynik.append(s)
else:
for c in range(k):
ciagi(s + str(c), n, k, wynik)
def podzbiory(t): # każdy podzbiór to jedna maska bitowa 0..2ⁿ-1
n = len(t)
wynik = []
for maska in range(2 ** n):
wynik.append([t[i] for i in range(n) if maska >> i & 1])
return wynik
def permutacje(t, k, wynik): # na pozycję k wstawiamy kolejno każdy pozostały element
if k == len(t):
wynik.append(t[:])
for i in range(k, len(t)):
t[k], t[i] = t[i], t[k]
permutacje(t, k + 1, wynik)
t[k], t[i] = t[i], t[k] # cofamy zamianę#include <string>
#include <vector>
#include <utility>
using namespace std;
void ciagi(string s, int n, int k, vector<string>& wynik) { // napisy długości n z cyfr 0..k-1
if ((int)s.size() == n) {
wynik.push_back(s);
return;
}
for (int c = 0; c < k; c++)
ciagi(s + to_string(c), n, k, wynik);
}
vector<vector<int>> podzbiory(const vector<int>& t) { // każdy podzbiór to maska 0..2ⁿ-1
int n = t.size();
vector<vector<int>> wynik;
for (int maska = 0; maska < (1 << n); maska++) {
vector<int> p;
for (int i = 0; i < n; i++)
if (maska >> i & 1) p.push_back(t[i]);
wynik.push_back(p);
}
return wynik;
}
void permutacje(vector<int>& t, int k, vector<vector<int>>& wynik) {
if (k == (int)t.size()) wynik.push_back(t);
for (int i = k; i < (int)t.size(); i++) { // na pozycję k każdy pozostały element
swap(t[k], t[i]);
permutacje(t, k + 1, wynik);
swap(t[k], t[i]); // cofamy zamianę
}
}procedura ciagi(s, n, k) // napisy długości n z cyfr 0..k−1
jeżeli długość(s) = n
wypisz s
w przeciwnym razie
dla c = 0, 1, …, k−1 wykonuj
ciagi(s + napis(c), n, k)
procedura podzbiory(T[0..n-1])
dla maska = 0, 1, …, 2ⁿ − 1 wykonuj
wypisz te T[i], dla których i-ty bit liczby maska to 1
procedura permutacje(T[0..n-1], k)
jeżeli k = n
wypisz T
dla i = k, …, n−1 wykonuj
zamień T[k] i T[i]
permutacje(T, k + 1)
zamień T[k] i T[i] // cofamy zamianęDla napisów długości n z k cyfr wynik ma kⁿ elementów. Drzewo wywołań ma 1 + k + k² + … + kⁿ węzłów, bo każde wywołanie poniżej ostatniego poziomu wywołuje k następnych. Podzbiorów zbioru n-elementowego jest 2ⁿ, a permutacji n!.
ciagi("", 2, 2)
├── ciagi("0") → "00", "01"
└── ciagi("1") → "10", "11"Ile napisów wypisze ciagi("", 3, 2)?
Ile łącznie wywołań funkcji ciagi (z pierwszym) będzie dla n = 2 i k = 3?
Ile jest podzbiorów zbioru 10-elementowego (z pustym)?
- 100
- 1024
- 3 628 800
Przeglądanie ciągu: wyszukiwanie, minimum, ciągi
Wiele zadań maturalnych to jeden przebieg po danych z kilkoma zmiennymi pomocniczymi. Najważniejsze jest wybranie, co trzeba pamiętać z poprzednich elementów.
def indeks(t, x):
for i in range(len(t)):
if t[i] == x:
return i
return -1 # nie ma takiego elementu
def minimum(t):
m = t[0]
for x in t:
if x < m:
m = x
return m
def najdluzszy_rosnacy(t):
# najdłuższy fragment kolejnych elementów, w którym każdy jest większy od poprzedniego
najl = dl = 1
for i in range(1, len(t)):
if t[i] > t[i - 1]:
dl += 1
else:
dl = 1
if dl > najl:
najl = dl
return najl#include <vector>
using namespace std;
int indeks(const vector<int>& t, int x) {
for (int i = 0; i < (int)t.size(); i++)
if (t[i] == x) return i;
return -1; // nie ma takiego elementu
}
int minimum(const vector<int>& t) {
int m = t[0];
for (int x : t)
if (x < m) m = x;
return m;
}
int najdluzszy_rosnacy(const vector<int>& t) {
// najdłuższy fragment kolejnych elementów, w którym każdy jest większy od poprzedniego
int najl = 1, dl = 1;
for (int i = 1; i < (int)t.size(); i++) {
if (t[i] > t[i - 1]) dl++;
else dl = 1;
if (dl > najl) najl = dl;
}
return najl;
}funkcja indeks(T[0..n-1], x)
dla i = 0, 1, …, n-1 wykonuj
jeżeli T[i] = x
zwróć i
zwróć −1 // nie ma takiego elementu
funkcja minimum(T[0..n-1])
m ← T[0]
dla i = 1, 2, …, n-1 wykonuj
jeżeli T[i] < m
m ← T[i]
zwróć m
funkcja najdluzszy_rosnacy(T[0..n-1])
najl ← 1; dl ← 1
dla i = 1, 2, …, n-1 wykonuj
jeżeli T[i] > T[i − 1]
dl ← dl + 1
w przeciwnym razie
dl ← 1
jeżeli dl > najl
najl ← dl
zwróć najlMinimum i maksimum naraz
Osobne szukanie minimum i maksimum to 2(n − 1) porównań. Gdy bierzemy elementy parami, porównujemy je ze sobą, a potem mniejszy tylko z minimum i większy tylko z maksimum. Na dwa elementy wypadają 3 porównania, łącznie około 3n/2.
def min_max(t):
n = len(t)
if n % 2 == 1:
mn = mx = t[0]
i = 1
else:
if t[0] < t[1]:
mn, mx = t[0], t[1]
else:
mn, mx = t[1], t[0]
i = 2
while i < n:
a, b = t[i], t[i + 1]
if a > b: # 1 porównanie w parze
a, b = b, a
if a < mn: # mniejszy z pary walczy tylko o minimum
mn = a
if b > mx: # większy tylko o maksimum
mx = b
i += 2
return mn, mx#include <vector>
#include <utility>
using namespace std;
pair<int, int> min_max(const vector<int>& t) {
int n = t.size(), mn, mx, i;
if (n % 2 == 1) {
mn = mx = t[0];
i = 1;
} else {
if (t[0] < t[1]) { mn = t[0]; mx = t[1]; }
else { mn = t[1]; mx = t[0]; }
i = 2;
}
for (; i < n; i += 2) {
int a = t[i], b = t[i + 1];
if (a > b) swap(a, b); // 1 porównanie w parze
if (a < mn) mn = a; // mniejszy z pary walczy tylko o minimum
if (b > mx) mx = b; // większy tylko o maksimum
}
return {mn, mx};
}funkcja min_max(T[0..n-1])
jeżeli n jest nieparzyste
mn ← T[0]; mx ← T[0]; i ← 1
w przeciwnym razie
jeżeli T[0] < T[1]
mn ← T[0]; mx ← T[1]
w przeciwnym razie
mn ← T[1]; mx ← T[0]
i ← 2
dopóki i < n wykonuj
a ← T[i]; b ← T[i + 1]
jeżeli a > b // 1 porównanie w parze
zamień a i b
jeżeli a < mn // mniejszy walczy tylko o minimum
mn ← a
jeżeli b > mx // większy tylko o maksimum
mx ← b
i ← i + 2
zwróć mn, mxJaka jest długość najdłuższego fragmentu rosnącego w ciągu 3 1 4 5 9 2 6 7?
Ile porównań wykona min_max dla 100 elementów?
- 198
- 150
- 148
- 100
Przesunięcie cykliczne
Przesunięcie tablicy o k miejsc w lewo, z przeniesieniem początku na koniec, to t[k:] + t[:k]. Tablica b jest przesunięciem a, gdy b == a[k:] + a[:k] dla jakiegoś k od 0 do n − 1. Sprawdzenie wszystkich k to O(n²). Szybciej: b musi wystąpić jako fragment tablicy a + a.
Przesunięcie tablicy o k miejsc w lewo: element t[(i + k) % n] trafia na pozycję i. Tablica b jest przesunięciem a, gdy dla jakiegoś k od 0 do n − 1 zachodzi b[i] == a[(i + k) % n] dla każdego i. Sprawdzenie wszystkich k to O(n²).
Tablicę 1 2 3 4 5 przesuwamy cyklicznie o 2 w lewo. Jaki jest wynik?
Sumy prefiksowe i fragmenty ciągu
Gdy trzeba wiele razy liczyć sumę fragmentu ciągu, raz liczymy sumy od początku. Potem każdą sumę fragmentu dostajemy jednym odejmowaniem.
def sumy_prefiksowe(t):
P = [0] * (len(t) + 1) # P[i] = t[0] + t[1] + ... + t[i-1]
for i in range(len(t)):
P[i + 1] = P[i] + t[i]
return P
# suma fragmentu t[a..b] (włącznie) to P[b + 1] - P[a]
def najlepsza_srednia(t, k): # fragment o co najmniej k elementach, O(n²)
P = sumy_prefiksowe(t)
suma, start, dl = P[k], 0, k
for a in range(len(t)):
for b in range(a + k, len(t) + 1): # fragment t[a..b-1]
s = P[b] - P[a]
if s * dl > suma * (b - a): # s/(b-a) > suma/dl bez dzielenia
suma, start, dl = s, a, b - a
return suma / dl, start, dl
def najwieksza_suma(t): # algorytm Kadane: najlepsza suma fragmentu, O(n)
naj = biezaca = t[0]
for x in t[1:]:
biezaca = max(x, biezaca + x) # przedłużamy fragment albo zaczynamy nowy
naj = max(naj, biezaca)
return naj#include <vector>
#include <algorithm>
using namespace std;
vector<long long> sumy_prefiksowe(const vector<int>& t) {
vector<long long> P(t.size() + 1, 0); // P[i] = t[0] + ... + t[i-1]
for (int i = 0; i < (int)t.size(); i++)
P[i + 1] = P[i] + t[i];
return P;
}
// suma fragmentu t[a..b] (włącznie) to P[b + 1] - P[a]
// fragment o co najmniej k elementach z największą średnią; ustawia start i dl
double najlepsza_srednia(const vector<int>& t, int k, int& start, int& dl) {
vector<long long> P = sumy_prefiksowe(t);
int n = t.size();
long long suma = P[k];
start = 0;
dl = k;
for (int a = 0; a < n; a++)
for (int b = a + k; b <= n; b++) { // fragment t[a..b-1]
long long s = P[b] - P[a];
if (s * dl > suma * (b - a)) { // s/(b-a) > suma/dl bez dzielenia
suma = s;
start = a;
dl = b - a;
}
}
return (double)suma / dl;
}
long long najwieksza_suma(const vector<int>& t) { // algorytm Kadane, O(n)
long long naj = t[0], biezaca = t[0];
for (int i = 1; i < (int)t.size(); i++) {
biezaca = max((long long)t[i], biezaca + t[i]); // przedłużamy albo od nowa
naj = max(naj, biezaca);
}
return naj;
}// P[i] = T[0] + … + T[i−1]; suma T[a..b] = P[b+1] − P[a]
funkcja sumy_prefiksowe(T[0..n-1])
P[0] ← 0
dla i = 0, 1, …, n−1 wykonuj
P[i + 1] ← P[i] + T[i]
zwróć P
funkcja najlepsza_srednia(T[0..n-1], k) // fragment ≥ k elementów
P ← sumy_prefiksowe(T)
suma ← P[k]; start ← 0; dl ← k
dla a = 0, 1, …, n−1 wykonuj
dla b = a+k, …, n wykonuj // fragment T[a..b−1]
s ← P[b] − P[a]
jeżeli s · dl > suma · (b − a)
suma ← s; start ← a; dl ← b − a
zwróć suma / dl, start, dl
funkcja najwieksza_suma(T[0..n-1]) // algorytm Kadane
naj ← T[0]; biezaca ← T[0]
dla i = 1, …, n−1 wykonuj
biezaca ← max(T[i], biezaca + T[i])
naj ← max(naj, biezaca)
zwróć naj| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| t[i] | 3 | −1 | 4 | 1 | −5 | 9 | 2 | −6 | |
| P[i] | 0 | 3 | 2 | 6 | 7 | 2 | 11 | 13 | 7 |
Suma t[2..5] to P[6] − P[2] = 11 − 2 = 9. Bez tablicy P sprawdzenie wszystkich fragmentów kosztowałoby O(n³), a z nią O(n²).
Średnie porównujemy bez dzielenia: s₁/d₁ > s₂/d₂ to to samo co s₁·d₂ > s₂·d₁. Wynik jest dokładny i przy remisie zostaje pierwszy znaleziony fragment, czego zwykle wymaga treść zadania.
Liczniki z przodu i z tyłu
Ta sama idea działa dla liczników: A[i] to liczba liter „a” wśród pierwszych i znaków, a B[i] to liczba liter „b” od i-tego znaku do końca. Wtedy dla każdego miejsca podziału słowa od razu wiadomo, ile jest „a” przed nim i „b” po nim.
Dla tablicy P powyżej: ile wynosi suma t[3..7]?
Jaka jest największa suma spójnego fragmentu ciągu 2, −3, 4, −1, 2, −5, 3?
Ile jest fragmentów (spójnych) w ciągu n-elementowym?
- n
- n(n+1)/2
- 2ⁿ
Wyszukiwanie binarne
W posortowanej tablicy każde porównanie ze środkiem odrzuca połowę kandydatów. Milion elementów przeszukasz w 20 krokach.
def szukaj_binarnie(t, x): # t musi być posortowana rosnąco
lewy, prawy = 0, len(t) - 1
while lewy <= prawy:
srodek = (lewy + prawy) // 2
if t[srodek] == x:
return srodek
if t[srodek] < x:
lewy = srodek + 1 # x może być tylko na prawo
else:
prawy = srodek - 1 # x może być tylko na lewo
return -1
def pierwszy_niemniejszy(t, x):
# indeks pierwszego elementu >= x; len(t), gdy takiego nie ma
lewy, prawy = 0, len(t)
while lewy < prawy:
s = (lewy + prawy) // 2
if t[s] < x:
lewy = s + 1
else:
prawy = s
return lewy#include <vector>
using namespace std;
int szukaj_binarnie(const vector<int>& t, int x) { // t musi być posortowana rosnąco
int lewy = 0, prawy = (int)t.size() - 1;
while (lewy <= prawy) {
int srodek = (lewy + prawy) / 2;
if (t[srodek] == x) return srodek;
if (t[srodek] < x) lewy = srodek + 1; // x może być tylko na prawo
else prawy = srodek - 1; // x może być tylko na lewo
}
return -1;
}
int pierwszy_niemniejszy(const vector<int>& t, int x) {
// indeks pierwszego elementu >= x; t.size(), gdy takiego nie ma
int lewy = 0, prawy = t.size();
while (lewy < prawy) {
int s = (lewy + prawy) / 2;
if (t[s] < x) lewy = s + 1;
else prawy = s;
}
return lewy;
}// T[0..n-1] posortowana rosnąco
funkcja szukaj_binarnie(T, x)
lewy ← 0; prawy ← n − 1
dopóki lewy ≤ prawy wykonuj
s ← (lewy + prawy) div 2
jeżeli T[s] = x
zwróć s
jeżeli T[s] < x
lewy ← s + 1 // x może być tylko na prawo
w przeciwnym razie
prawy ← s − 1 // x może być tylko na lewo
zwróć −1
funkcja pierwszy_niemniejszy(T, x)
lewy ← 0; prawy ← n
dopóki lewy < prawy wykonuj
s ← (lewy + prawy) div 2
jeżeli T[s] < x
lewy ← s + 1
w przeciwnym razie
prawy ← s
zwróć lewy| krok | lewy | prawy | srodek | t[srodek] |
|---|---|---|---|---|
| 1 | 0 | 9 | 4 | 9 |
| 2 | 5 | 9 | 7 | 15 |
| 3 | 5 | 6 | 5 | 11 — znaleziono |
Ślad dla t = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] i szukanego x = 11.
Wariant „pierwszy element ≥ x” jest często bardziej przydatny: mówi, gdzie wstawić x albo ile elementów jest mniejszych od x. Ta sama idea działa dla dowolnej funkcji monotonicznej, np. przy szukaniu miejsca zerowego.
Szukanie po wyniku
Wyszukiwać binarnie można nie tylko w tablicy, ale też wśród możliwych wyników, jeśli warunek „wynik jest za mały” zmienia się tylko raz: najpierw jest prawdziwy, potem fałszywy. Tak liczy się np. całkowity pierwiastek bez liczb zmiennoprzecinkowych.
def pierwiastek3(n): # najmniejsze p ≥ 1, dla którego p³ ≥ n
lewy, prawy = 1, n
while lewy < prawy:
s = (lewy + prawy) // 2
if s * s * s < n:
lewy = s + 1 # s za małe
else:
prawy = s # s może być wynikiem
return lewylong long pierwiastek3(long long n) { // najmniejsze p >= 1, dla którego p³ >= n
long long lewy = 1, prawy = n;
while (lewy < prawy) {
long long s = (lewy + prawy) / 2;
if (s * s * s < n) lewy = s + 1; // s za małe
else prawy = s; // s może być wynikiem
}
return lewy;
}funkcja pierwiastek3(n) // najmniejsze p, dla którego p³ ≥ n
lewy ← 1; prawy ← n
dopóki lewy < prawy wykonuj
s ← (lewy + prawy) div 2
jeżeli s · s · s < n
lewy ← s + 1
w przeciwnym razie
prawy ← s
zwróć lewyJaki wynik da pierwiastek3(100)?
Ciąg ma najpierw same liczby nieparzyste, a potem same parzyste. Jak najszybciej znaleźć pierwszą parzystą?
- Przejrzeć wszystkie liczby od początku
- Wyszukiwaniem binarnym: warunek „t[s] jest nieparzyste” zmienia się tylko raz
- Posortować ciąg
Ile co najwyżej porównań ze środkiem wykona wyszukiwanie binarne w tablicy 1000 elementów?
Jaki indeks zwróci pierwszy_niemniejszy([2, 4, 4, 8, 10], 5)?
Sortowanie bąbelkowe, przez wybór i przez wstawianie
Trzy algorytmy O(n²), które trzeba umieć prześledzić na papierze. Różnią się tym, co dzieje się w jednym przebiegu.
| Algorytm | Pomysł | Najlepszy przypadek |
|---|---|---|
| bąbelkowe | zamieniaj sąsiadów w złej kolejności; największy „wypływa” na koniec | O(n), gdy dane są posortowane (z flagą zamiany) |
| przez wybór | znajdź minimum reszty i postaw je na początku | zawsze O(n²), ale tylko n − 1 zamian |
| przez wstawianie | wstaw kolejny element w posortowany początek | O(n), gdy dane są prawie posortowane |
def babelkowe(t):
n = len(t)
for i in range(n - 1):
zamiana = False
for j in range(n - 1 - i): # ostatnie i elementów już stoi na miejscu
if t[j] > t[j + 1]:
t[j], t[j + 1] = t[j + 1], t[j]
zamiana = True
if not zamiana: # przebieg bez zamian: koniec
break
def przez_wybor(t):
n = len(t)
for i in range(n - 1):
m = i
for j in range(i + 1, n): # szukamy minimum w części nieposortowanej
if t[j] < t[m]:
m = j
t[i], t[m] = t[m], t[i]
def przez_wstawianie(t):
for i in range(1, len(t)):
x = t[i]
j = i - 1
while j >= 0 and t[j] > x: # przesuwamy większe w prawo
t[j + 1] = t[j]
j -= 1
t[j + 1] = x#include <vector>
#include <utility>
using namespace std;
void babelkowe(vector<int>& t) {
int n = t.size();
for (int i = 0; i < n - 1; i++) {
bool zamiana = false;
for (int j = 0; j < n - 1 - i; j++) // ostatnie i elementów już stoi na miejscu
if (t[j] > t[j + 1]) {
swap(t[j], t[j + 1]);
zamiana = true;
}
if (!zamiana) break; // przebieg bez zamian: koniec
}
}
void przez_wybor(vector<int>& t) {
int n = t.size();
for (int i = 0; i < n - 1; i++) {
int m = i;
for (int j = i + 1; j < n; j++) // szukamy minimum w części nieposortowanej
if (t[j] < t[m]) m = j;
swap(t[i], t[m]);
}
}
void przez_wstawianie(vector<int>& t) {
for (int i = 1; i < (int)t.size(); i++) {
int x = t[i], j = i - 1;
while (j >= 0 && t[j] > x) { // przesuwamy większe w prawo
t[j + 1] = t[j];
j--;
}
t[j + 1] = x;
}
}procedura babelkowe(T[0..n-1])
dla i = 0, 1, …, n-2 wykonuj
zamiana ← FAŁSZ
dla j = 0, 1, …, n-2-i wykonuj
jeżeli T[j] > T[j + 1]
zamień T[j] i T[j + 1]
zamiana ← PRAWDA
jeżeli nie zamiana
zakończ // przebieg bez zamian
procedura przez_wybor(T[0..n-1])
dla i = 0, 1, …, n-2 wykonuj
m ← i
dla j = i+1, …, n-1 wykonuj // minimum części nieposortowanej
jeżeli T[j] < T[m]
m ← j
zamień T[i] i T[m]
procedura przez_wstawianie(T[0..n-1])
dla i = 1, 2, …, n-1 wykonuj
x ← T[i]
j ← i − 1
dopóki j ≥ 0 oraz T[j] > x wykonuj
T[j + 1] ← T[j] // przesuwamy większe w prawo
j ← j − 1
T[j + 1] ← xstart: 5 1 4 2 8
po przebiegu: 1 4 2 5 8
po przebiegu: 1 2 4 5 8
po przebiegu: 1 2 4 5 8 (bez zamian, koniec)Które sortowanie jest najszybsze dla danych prawie posortowanych?
- przez wybór
- przez wstawianie
- wszystkie tak samo
Jak wygląda tablica 7 3 5 1 po dwóch pierwszych krokach sortowania przez wybór? Wpisz liczby oddzielone spacjami.
Ile zamian sąsiadów wykona sortowanie bąbelkowe dla tablicy 4 3 2 1?
Sortowanie przez scalanie
Dziel i zwyciężaj: podziel tablicę na połowy, posortuj każdą, a potem scal dwie posortowane listy w jedną.
Scalanie przegląda obie listy po razie, zawsze zabierając mniejszy z dwóch czołowych elementów, więc kosztuje O(n). Poziomów podziału jest log₂ n. Razem daje to O(n log n) w każdym przypadku.
def scal(a, b): # a i b są już posortowane
wynik = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] <= b[j]:
wynik.append(a[i])
i += 1
else:
wynik.append(b[j])
j += 1
wynik.extend(a[i:]) # resztka jednej z list
wynik.extend(b[j:])
return wynik
def sortuj_scalanie(t):
if len(t) <= 1:
return t
s = len(t) // 2
return scal(sortuj_scalanie(t[:s]), sortuj_scalanie(t[s:]))#include <vector>
using namespace std;
// sortuje t[lewy..prawy] włącznie
void sortuj_scalanie(vector<int>& t, int lewy, int prawy) {
if (lewy >= prawy) return;
int s = (lewy + prawy) / 2;
sortuj_scalanie(t, lewy, s);
sortuj_scalanie(t, s + 1, prawy);
vector<int> pom; // scalanie dwóch posortowanych połówek
int i = lewy, j = s + 1;
while (i <= s && j <= prawy) {
if (t[i] <= t[j]) pom.push_back(t[i++]);
else pom.push_back(t[j++]);
}
while (i <= s) pom.push_back(t[i++]);
while (j <= prawy) pom.push_back(t[j++]);
for (int k = 0; k < (int)pom.size(); k++)
t[lewy + k] = pom[k];
}funkcja scal(A, B) // A i B posortowane
W ← pusta lista; i ← 0; j ← 0
dopóki i < |A| oraz j < |B| wykonuj
jeżeli A[i] ≤ B[j]
dopisz A[i] do W; i ← i + 1
w przeciwnym razie
dopisz B[j] do W; j ← j + 1
dopisz do W pozostałe elementy A i B
zwróć W
funkcja sortuj_scalanie(T)
jeżeli |T| ≤ 1
zwróć T
s ← |T| div 2
zwróć scal(sortuj_scalanie(T[0..s-1]), sortuj_scalanie(T[s..|T|-1]))[5, 2, 4, 7, 1, 3, 2, 6]
[5, 2, 4, 7] [1, 3, 2, 6]
[5, 2] [4, 7] [1, 3] [2, 6]
[2, 5] [4, 7] [1, 3] [2, 6]
[2, 4, 5, 7] [1, 2, 3, 6]
[1, 2, 2, 3, 4, 5, 6, 7]<= przy scalaniu zachowuje kolejność równych elementów. Ma to znaczenie, gdy sortujesz np. uczniów po ocenie i chcesz zachować kolejność alfabetyczną.Wynikiem scalenia list [1, 4, 9] i [2, 3, 10] jest lista… Wpisz liczby oddzielone spacjami.
Ile co najwyżej porównań wymaga scalenie dwóch posortowanych list po 5 elementów?
- 5
- 9
- 10
- 25
Sortowanie szybkie (quicksort)
Wybierz element dzielący (pivot), ustaw mniejsze przed nim, a większe za nim, i posortuj obie części tak samo.
def quicksort(t, lewy, prawy):
if lewy >= prawy:
return
pivot = t[prawy] # element dzielący: ostatni
i = lewy
for j in range(lewy, prawy):
if t[j] < pivot: # mniejsze od pivota idą na początek
t[i], t[j] = t[j], t[i]
i += 1
t[i], t[prawy] = t[prawy], t[i] # pivot na swoje ostateczne miejsce
quicksort(t, lewy, i - 1)
quicksort(t, i + 1, prawy)
# wywołanie: quicksort(t, 0, len(t) - 1)#include <vector>
#include <utility>
using namespace std;
void quicksort(vector<int>& t, int lewy, int prawy) {
if (lewy >= prawy) return;
int pivot = t[prawy]; // element dzielący: ostatni
int i = lewy;
for (int j = lewy; j < prawy; j++)
if (t[j] < pivot) // mniejsze od pivota idą na początek
swap(t[i++], t[j]);
swap(t[i], t[prawy]); // pivot na swoje ostateczne miejsce
quicksort(t, lewy, i - 1);
quicksort(t, i + 1, prawy);
}
// wywołanie: quicksort(t, 0, t.size() - 1);procedura quicksort(T, lewy, prawy)
jeżeli lewy ≥ prawy
zakończ
pivot ← T[prawy] // element dzielący: ostatni
i ← lewy
dla j = lewy, …, prawy−1 wykonuj
jeżeli T[j] < pivot // mniejsze idą na początek
zamień T[i] i T[j]
i ← i + 1
zamień T[i] i T[prawy] // pivot na swoje miejsce
quicksort(T, lewy, i − 1)
quicksort(T, i + 1, prawy)Średnio quicksort działa w O(n log n) i w praktyce jest bardzo szybki. Przy złym wyborze pivota jedna część jest pusta, a druga ma n − 1 elementów. Tak dzieje się np. dla posortowanej tablicy i pivota równego ostatniemu elementowi. Wtedy złożoność spada do O(n²).
t = [3, 8, 2, 5, 1, 4], pivot = 4
po podziale: [3, 2, 1, 4, 8, 5]
mniejsze ^ pivot ^ większesorted(t) albo t.sort(). Własne sortowanie pisz tylko wtedy, gdy wymaga tego treść zadania.sort(t.begin(), t.end()) z biblioteki <algorithm>. Własne sortowanie pisz tylko wtedy, gdy wymaga tego treść zadania.Kiedy quicksort z pivotem równym ostatniemu elementowi działa najwolniej?
- dla danych losowych
- dla danych już posortowanych
- dla tablicy z dwiema wartościami
Na której pozycji (od 0) stanie pivot 4 po podziale tablicy [3, 8, 2, 5, 1, 4]?
Sortowanie przez zliczanie i kubełkowe
Gdy wartości są małymi liczbami całkowitymi, nie trzeba ich porównywać. Wystarczy policzyć, ile razy występuje każda z nich.
def przez_zliczanie(t, maks): # wszystkie liczby z przedziału 0..maks
ile = [0] * (maks + 1)
for x in t:
ile[x] += 1
wynik = []
for v in range(maks + 1):
wynik.extend([v] * ile[v])
return wynik
def kubelkowe(t, k=10): # liczby rzeczywiste z przedziału [0, 1)
kubelki = [[] for _ in range(k)]
for x in t:
kubelki[int(x * k)].append(x)
wynik = []
for kub in kubelki:
kub.sort() # kubełki są małe, wystarczy dowolne proste sortowanie
wynik.extend(kub)
return wynik#include <vector>
#include <algorithm>
using namespace std;
vector<int> przez_zliczanie(const vector<int>& t, int maks) { // liczby 0..maks
vector<int> ile(maks + 1, 0);
for (int x : t) ile[x]++;
vector<int> wynik;
for (int v = 0; v <= maks; v++)
for (int k = 0; k < ile[v]; k++)
wynik.push_back(v);
return wynik;
}
vector<double> kubelkowe(const vector<double>& t, int k = 10) { // liczby z [0, 1)
vector<vector<double>> kubelki(k);
for (double x : t) kubelki[(int)(x * k)].push_back(x);
vector<double> wynik;
for (auto& kub : kubelki) {
sort(kub.begin(), kub.end()); // kubełki są małe
for (double x : kub) wynik.push_back(x);
}
return wynik;
}// wszystkie liczby z przedziału 0..maks
funkcja przez_zliczanie(T, maks)
ILE[0..maks] ← 0
dla każdego x w T wykonuj
ILE[x] ← ILE[x] + 1
W ← pusta lista
dla v = 0, 1, …, maks wykonuj
dopisz v do W ILE[v] razy
zwróć W
// liczby rzeczywiste z przedziału [0, 1), k kubełków
funkcja kubelkowe(T, k)
K[0..k-1] ← puste listy
dla każdego x w T wykonuj
dopisz x do K[⌊x · k⌋]
posortuj każdy kubełek (np. przez wstawianie)
zwróć połączone K[0], K[1], …, K[k-1]Sortowanie przez zliczanie ma złożoność O(n + k), gdzie k to zakres wartości. Świetnie sprawdza się dla ocen 1–6, wieku albo liczb do kilku tysięcy. Przy wartościach do miliarda tablica byłaby za duża.
Sortowanie kubełkowe dzieli zakres na przedziały (kubełki), sortuje każdy osobno i łączy wyniki. Dla równomiernie rozłożonych danych kubełki są małe, a całość działa średnio w O(n).
ile od razu odpowiada na pytania „która wartość występuje najczęściej” i „ile jest różnych wartości”. To częste zadania maturalne.Tablica ocen to 5 3 5 6 3 5. Ile wynosi ile[5]?
Kiedy sortowanie przez zliczanie się nie opłaca?
- gdy liczb jest dużo
- gdy zakres wartości jest ogromny w porównaniu z liczbą elementów
- gdy wartości się powtarzają
Napisy: palindromy, anagramy, wzorce
Napis to tablica znaków, a każdy znak ma kod liczbowy. Większość algorytmów tekstowych to pętla po indeksach i porównywanie znaków.
| Znak | Kod ASCII | Uwagi |
|---|---|---|
0…9 | 48…57 | cyfra = kod − 48 |
A…Z | 65…90 | wielkie litery są „mniejsze” od małych |
a…z | 97…122 | mała = wielka + 32 |
def palindrom(s):
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
def anagramy(a, b): # małe litery a-z
if len(a) != len(b):
return False
ile = [0] * 26
for z in a:
ile[ord(z) - ord("a")] += 1
for z in b:
ile[ord(z) - ord("a")] -= 1
for x in ile:
if x != 0:
return False
return True
def wystapienia(tekst, wzorzec): # wyszukiwanie naiwne
wynik = []
n, m = len(tekst), len(wzorzec)
for i in range(n - m + 1):
j = 0
while j < m and tekst[i + j] == wzorzec[j]:
j += 1
if j == m:
wynik.append(i)
return wynik#include <string>
#include <vector>
using namespace std;
bool palindrom(const string& s) {
int i = 0, j = (int)s.size() - 1;
while (i < j) {
if (s[i] != s[j]) return false;
i++;
j--;
}
return true;
}
bool anagramy(const string& a, const string& b) { // małe litery a-z
if (a.size() != b.size()) return false;
int ile[26] = {0};
for (char z : a) ile[z - 'a']++;
for (char z : b) ile[z - 'a']--;
for (int x : ile)
if (x != 0) return false;
return true;
}
vector<int> wystapienia(const string& tekst, const string& wzorzec) { // naiwnie
vector<int> wynik;
int n = tekst.size(), m = wzorzec.size();
for (int i = 0; i + m <= n; i++) {
int j = 0;
while (j < m && tekst[i + j] == wzorzec[j]) j++;
if (j == m) wynik.push_back(i);
}
return wynik;
}funkcja palindrom(s[0..n-1])
i ← 0; j ← n − 1
dopóki i < j wykonuj
jeżeli s[i] ≠ s[j]
zwróć FAŁSZ
i ← i + 1; j ← j − 1
zwróć PRAWDA
funkcja anagramy(a, b) // małe litery a–z
jeżeli |a| ≠ |b|
zwróć FAŁSZ
ILE[0..25] ← 0
dla każdej litery z w a: ILE[kod(z) − kod('a')] ← ILE[…] + 1
dla każdej litery z w b: ILE[kod(z) − kod('a')] ← ILE[…] − 1
zwróć PRAWDA, gdy wszystkie ILE[i] = 0
funkcja wystapienia(tekst[0..n-1], wzorzec[0..m-1])
W ← pusta lista
dla i = 0, 1, …, n−m wykonuj
j ← 0
dopóki j < m oraz tekst[i + j] = wzorzec[j] wykonuj
j ← j + 1
jeżeli j = m
dopisz i do W
zwróć WAnagramy to słowa z tych samych liter w innej kolejności, np. kot i tok. Zliczanie liter działa w O(n). Porównanie posortowanych słów (sorted(a) == sorted(b)) też jest poprawne, ale wolniejsze: O(n log n).
Porządek leksykograficzny to porządek słownikowy według kodów znaków. W Pythonie i C++ porównujesz napisy zwykłym <, ale "Zebra" < "ala" jest prawdą, bo Z ma kod 90, a a 97.
Pod jakimi indeksami (od 0) wzorzec aba występuje w tekście abababa? Wpisz liczby oddzielone spacjami.
Które porównanie jest prawdziwe?
"ala" < "Ala""Ala" < "ala"- Są równe
Początek i koniec słowa
Czy słowo a zaczyna się słowem p: a[:len(p)] == p albo a.startswith(p). Czy kończy się: a.endswith(p). Najdłuższy fragment, który jest początkiem a i końcem b, znajdziesz, sprawdzając długości d od największej: a[:d] == b[len(b) - d:].
Czy słowo a zaczyna się słowem p: a.compare(0, p.size(), p) == 0 albo a.substr(0, p.size()) == p. Najdłuższy fragment, który jest początkiem a i końcem b, znajdziesz, sprawdzając długości d od największej: a.substr(0, d) == b.substr(b.size() - d).
Jaka jest długość najdłuższego fragmentu, który jest początkiem słowa abcab i końcem słowa xxab?
Czy kajak i kajaki są anagramami?
- Tak
- Nie
Szyfry: Cezar, Vigenère, przestawieniowy
Szyfry podstawieniowe zamieniają każdą literę na inną. Szyfry przestawieniowe zostawiają litery, ale zmieniają ich kolejność.
def cezar(tekst, k): # wielkie litery A-Z, reszta bez zmian
wynik = ""
for z in tekst:
if "A" <= z <= "Z":
wynik += chr((ord(z) - 65 + k) % 26 + 65)
else:
wynik += z
return wynik
# odszyfrowanie: cezar(szyfrogram, -k)
def vigenere(tekst, klucz, kierunek=1): # kierunek -1 odszyfrowuje
wynik = ""
for i in range(len(tekst)):
k = ord(klucz[i % len(klucz)]) - 65
wynik += chr((ord(tekst[i]) - 65 + kierunek * k) % 26 + 65)
return wynik
def przestaw_pary(tekst): # szyfr przestawieniowy: zamiana sąsiednich znaków
t = list(tekst)
for i in range(0, len(t) - 1, 2):
t[i], t[i + 1] = t[i + 1], t[i]
return "".join(t)#include <string>
#include <utility>
using namespace std;
string cezar(const string& tekst, int k) { // wielkie litery A-Z, reszta bez zmian
string wynik = tekst;
k = (k % 26 + 26) % 26; // w C++ reszta z liczby ujemnej jest ujemna
for (char& z : wynik)
if (z >= 'A' && z <= 'Z')
z = (z - 'A' + k) % 26 + 'A';
return wynik;
}
// odszyfrowanie: cezar(szyfrogram, -k)
string vigenere(const string& tekst, const string& klucz, int kierunek = 1) {
string wynik = tekst;
for (int i = 0; i < (int)tekst.size(); i++) {
int k = klucz[i % klucz.size()] - 'A';
wynik[i] = ((tekst[i] - 'A' + kierunek * k) % 26 + 26) % 26 + 'A';
}
return wynik;
}
string przestaw_pary(string t) { // szyfr przestawieniowy: zamiana sąsiednich znaków
for (int i = 0; i + 1 < (int)t.size(); i += 2)
swap(t[i], t[i + 1]);
return t;
}funkcja cezar(tekst, k) // wielkie litery A–Z
dla każdego znaku z w tekst wykonuj
jeżeli z jest literą A–Z
z ← znak o kodzie ((kod(z) − 65 + k) mod 26) + 65
zwróć tekst
// odszyfrowanie: cezar(szyfrogram, −k), mod daje wynik 0..25
funkcja vigenere(tekst[0..n-1], klucz[0..d-1], kierunek)
dla i = 0, 1, …, n-1 wykonuj
k ← kod(klucz[i mod d]) − 65
tekst[i] ← znak o kodzie ((kod(tekst[i]) − 65 + kierunek · k) mod 26) + 65
zwróć tekst
funkcja przestaw_pary(t[0..n-1]) // zamiana sąsiednich znaków
i ← 0
dopóki i + 1 < n wykonuj
zamień t[i] i t[i + 1]
i ← i + 2
zwróć tW szyfrze Cezara każdą literę przesuwamy o k pozycji w alfabecie, a po Z wracamy do A dzięki reszcie z dzielenia przez 26. Szyfr Vigenère’a używa innego przesunięcia dla każdej litery, wziętego z kolejnej litery klucza (A = 0, B = 1, …).
-3 % 26 == 23, więc odszyfrowanie przez cezar(t, -k) działa od razu.-3 % 26 == -3. Przy odszyfrowaniu zawsze używaj (x % 26 + 26) % 26.mod daje wynik od 0 do 25. W programie sprawdź, jak działa reszta w Twoim języku.Zaszyfruj szyfrem Cezara z k = 3 tekst ABC XYZ.
Zaszyfruj szyfrem Vigenère’a tekst MATURA kluczem AB.
Co da przestaw_pary("INFORMATYKA")?
Tablice dwuwymiarowe: plansze i obrazy
Plik z wierszami jednakowej długości to plansza: T[i][j] to znak w wierszu i, kolumnie j. Sąsiadów pola liczymy przesunięciami wiersza i kolumny.
Wczytanie: T = [w.strip() for w in open("plansza.txt")]. Liczba wierszy to len(T), kolumn len(T[0]).
Wczytanie: vector<string> T; string w; while (we >> w) T.push_back(w);. Liczba wierszy to T.size(), kolumn T[0].size().
from collections import deque
def kwadraty(T): # środki kwadratów 3×3 z jednakowych znaków
wynik = []
for i in range(1, len(T) - 1):
for j in range(1, len(T[0]) - 1):
dobry = True
for di in (-1, 0, 1):
for dj in (-1, 0, 1):
if T[i + di][j + dj] != T[i][j]:
dobry = False
if dobry:
wynik.append((i, j))
return wynik
def dojscie(A): # czy z lewego górnego rogu dojdziemy do prawego dolnego,
n, m = len(A), len(A[0]) # idąc w prawo lub w dół tylko po polach 1
P = [[False] * m for _ in range(n)]
for i in range(n):
for j in range(m):
if A[i][j] == 1:
if i == 0 and j == 0:
P[i][j] = True
else:
P[i][j] = (i > 0 and P[i - 1][j]) or (j > 0 and P[i][j - 1])
return P[n - 1][m - 1]
def obszar(T, i, j): # BFS: wielkość obszaru takich samych znaków wokół (i, j)
znak = T[i][j]
odwiedzone = {(i, j)}
kolejka = deque([(i, j)])
while kolejka:
y, x = kolejka.popleft()
for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
ny, nx = y + dy, x + dx
if 0 <= ny < len(T) and 0 <= nx < len(T[0]) \
and (ny, nx) not in odwiedzone and T[ny][nx] == znak:
odwiedzone.add((ny, nx))
kolejka.append((ny, nx))
return len(odwiedzone)#include <string>
#include <vector>
#include <queue>
#include <utility>
using namespace std;
vector<pair<int, int>> kwadraty(const vector<string>& T) { // środki kwadratów 3×3
vector<pair<int, int>> wynik;
for (int i = 1; i + 1 < (int)T.size(); i++)
for (int j = 1; j + 1 < (int)T[0].size(); j++) {
bool dobry = true;
for (int di = -1; di <= 1; di++)
for (int dj = -1; dj <= 1; dj++)
if (T[i + di][j + dj] != T[i][j]) dobry = false;
if (dobry) wynik.push_back({i, j});
}
return wynik;
}
bool dojscie(const vector<vector<int>>& A) { // w prawo lub w dół, tylko po polach 1
int n = A.size(), m = A[0].size();
vector<vector<bool>> P(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (A[i][j] == 1) {
if (i == 0 && j == 0) P[i][j] = true;
else P[i][j] = (i > 0 && P[i - 1][j]) || (j > 0 && P[i][j - 1]);
}
return P[n - 1][m - 1];
}
int obszar(const vector<string>& T, int i, int j) { // BFS: wielkość obszaru wokół (i, j)
int n = T.size(), m = T[0].size(), ile = 1;
vector<vector<bool>> odw(n, vector<bool>(m, false));
queue<pair<int, int>> kolejka;
odw[i][j] = true;
kolejka.push({i, j});
int dy[] = {-1, 1, 0, 0}, dx[] = {0, 0, -1, 1};
while (!kolejka.empty()) {
auto [y, x] = kolejka.front();
kolejka.pop();
for (int k = 0; k < 4; k++) {
int ny = y + dy[k], nx = x + dx[k];
if (ny >= 0 && ny < n && nx >= 0 && nx < m && !odw[ny][nx] && T[ny][nx] == T[i][j]) {
odw[ny][nx] = true;
ile++;
kolejka.push({ny, nx});
}
}
}
return ile;
}funkcja kwadraty(T[0..n-1][0..m-1]) // środki kwadratów 3×3
dla i = 1, …, n−2 wykonuj
dla j = 1, …, m−2 wykonuj
jeżeli wszystkie T[i+di][j+dj] = T[i][j] dla di, dj ∈ {−1, 0, 1}
dopisz (i, j) do wyniku
funkcja dojscie(A[0..n-1][0..m-1]) // w prawo lub w dół po polach 1
dla i = 0, …, n−1 wykonuj
dla j = 0, …, m−1 wykonuj
jeżeli A[i][j] = 0
P[i][j] ← FAŁSZ
w przeciwnym razie jeżeli i = 0 oraz j = 0
P[i][j] ← PRAWDA
w przeciwnym razie
P[i][j] ← (i > 0 oraz P[i−1][j]) lub (j > 0 oraz P[i][j−1])
zwróć P[n−1][m−1]
funkcja obszar(T, i, j) // przeszukiwanie wszerz (BFS)
oznacz (i, j) jako odwiedzone; wstaw (i, j) do kolejki
dopóki kolejka niepusta wykonuj
(y, x) ← wyjmij z kolejki
dla każdego sąsiada (y', x') z góry, dołu, lewej i prawej wykonuj
jeżeli (y', x') na planszy, nieodwiedzone i T[y'][x'] = T[i][j]
oznacz jako odwiedzone; wstaw do kolejki
zwróć liczbę odwiedzonych pólKwadrat 3×3 sprawdzamy dla każdego pola, które ma sąsiadów ze wszystkich stron, czyli bez pierwszego i ostatniego wiersza i kolumny. To częsta pułapka: wyjście poza tablicę.
W dojscie pole jest osiągalne, jeśli jest białe i osiągalne jest pole nad nim albo na lewo od niego. Wypełniamy tablicę wierszami, więc obie wartości są już policzone. To programowanie dynamiczne na planszy.
Gdy ruchy są dowolne (góra, dół, lewo, prawo), używamy przeszukiwania wszerz (BFS): kolejka pól do odwiedzenia i zbiór już odwiedzonych. Tak liczy się wielkość jednolitego obszaru albo najkrótszą drogę.
Ile kwadratów 3×3 z jednakowych znaków jest na planszy 4×4 wypełnionej samymi x?
Plansza (1 = białe): 110 / 010 / 011. Czy da się przejść z lewego górnego do prawego dolnego rogu, idąc tylko w prawo i w dół po białych polach?
- Tak
- Nie
Drzewa: BST, kopiec i hierarchia
Drzewo binarne mieści się w zwykłej tablicy: korzeń ma indeks 1, a dzieci węzła i mają indeksy 2i oraz 2i + 1. Rodzic węzła s to s div 2.
W drzewie BST mniejsze liczby idą do lewego poddrzewa, a większe do prawego. Przechodząc drzewo w kolejności lewe poddrzewo, węzeł, prawe (inorder), wypisujemy liczby rosnąco. W kopcu każdy rodzic jest większy od dzieci, więc największy element jest w korzeniu. Nowy element wstawiamy na koniec i zamieniamy z rodzicem, dopóki jest od niego większy.
def wstaw_bst(D, x): # drzewo w tablicy: korzeń D[1], dzieci węzła i to 2i i 2i+1
i = 1
while i < len(D) and D[i] is not None:
if x < D[i]:
i = 2 * i # mniejsze w lewo
else:
i = 2 * i + 1 # większe w prawo
while len(D) <= i:
D.append(None)
D[i] = x
def inorder(D, i, wynik): # lewe poddrzewo, węzeł, prawe: daje liczby rosnąco
if i < len(D) and D[i] is not None:
inorder(D, 2 * i, wynik)
wynik.append(D[i])
inorder(D, 2 * i + 1, wynik)
def wstaw_kopiec(T, x): # kopiec w T[1..n]: rodzic węzła s to s // 2
T.append(x)
s = len(T) - 1
while s > 1 and T[s] > T[s // 2]: # większy od rodzica wędruje w górę
T[s], T[s // 2] = T[s // 2], T[s]
s //= 2
def hierarchia(szef): # szef[x] < x to bezpośredni przełożony x; szef[1] = 0
n = len(szef) - 1
dzieci = [0] * (n + 1) # bezpośredni podwładni
wszyscy = [0] * (n + 1) # wszyscy podwładni
glebokosc = [0] * (n + 1) # liczba przełożonych
for x in range(2, n + 1):
dzieci[szef[x]] += 1
glebokosc[x] = glebokosc[szef[x]] + 1 # szef ma mniejszy numer: już policzony
for x in range(n, 1, -1): # od końca: podwładni x są już zliczeni
wszyscy[szef[x]] += wszyscy[x] + 1
return dzieci, wszyscy, glebokosc#include <vector>
#include <utility>
using namespace std;
const int PUSTE = -1; // drzewo w tablicy: korzeń D[1], dzieci węzła i to 2i i 2i+1
void wstaw_bst(vector<int>& D, int x) {
int i = 1;
while (i < (int)D.size() && D[i] != PUSTE)
i = x < D[i] ? 2 * i : 2 * i + 1; // mniejsze w lewo, większe w prawo
if ((int)D.size() <= i) D.resize(i + 1, PUSTE);
D[i] = x;
}
void inorder(const vector<int>& D, int i, vector<int>& wynik) { // daje liczby rosnąco
if (i >= (int)D.size() || D[i] == PUSTE) return;
inorder(D, 2 * i, wynik);
wynik.push_back(D[i]);
inorder(D, 2 * i + 1, wynik);
}
void wstaw_kopiec(vector<int>& T, int x) { // kopiec w T[1..n]: rodzic s to s / 2
T.push_back(x);
int s = T.size() - 1;
while (s > 1 && T[s] > T[s / 2]) { // większy od rodzica wędruje w górę
swap(T[s], T[s / 2]);
s /= 2;
}
}
// szef[x] < x to bezpośredni przełożony x; szef[1] = 0
void hierarchia(const vector<int>& szef, vector<int>& dzieci, vector<int>& wszyscy, vector<int>& glebokosc) {
int n = szef.size() - 1;
dzieci.assign(n + 1, 0);
wszyscy.assign(n + 1, 0);
glebokosc.assign(n + 1, 0);
for (int x = 2; x <= n; x++) {
dzieci[szef[x]]++;
glebokosc[x] = glebokosc[szef[x]] + 1; // szef ma mniejszy numer: już policzony
}
for (int x = n; x >= 2; x--) // od końca: podwładni x są już zliczeni
wszyscy[szef[x]] += wszyscy[x] + 1;
}// drzewo w tablicy: korzeń D[1], dzieci węzła i to 2i oraz 2i+1
procedura wstaw_bst(D, x)
i ← 1
dopóki D[i] niepuste wykonuj
jeżeli x < D[i]
i ← 2 · i
w przeciwnym razie
i ← 2 · i + 1
D[i] ← x
procedura inorder(D, i) // wypisuje liczby rosnąco
jeżeli D[i] niepuste
inorder(D, 2 · i)
wypisz D[i]
inorder(D, 2 · i + 1)
procedura wstaw_kopiec(T[1..n], x) // rodzic węzła s to s div 2
n ← n + 1; T[n] ← x; s ← n
dopóki s > 1 oraz T[s] > T[s div 2] wykonuj
zamień T[s] i T[s div 2]
s ← s div 2
procedura hierarchia(szef[1..n]) // szef[x] < x
dla x = 2, …, n wykonuj
dzieci[szef[x]] ← dzieci[szef[x]] + 1
glebokosc[x] ← glebokosc[szef[x]] + 1
dla x = n, n−1, …, 2 wykonuj
wszyscy[szef[x]] ← wszyscy[szef[x]] + wszyscy[x] + 1wstawiane: 10, 4, 15, 12, 2, 7, 20
10 D[1]
4 15 D[2], D[3]
2 7 12 20 D[4] … D[7]Wstawianie liczb już posortowanych tworzy w BST jedną długą gałąź: 1, 2, 3 trafiają pod indeksy 1, 3 i 7. Tablica rośnie wtedy wykładniczo, a drzewo ma wysokość n.
Hierarchia zapisana tablicą przełożonych
Gdy dla każdego węzła znamy tylko rodzica i rodzic ma zawsze mniejszy numer, nie trzeba budować drzewa. Przechodząc numery rosnąco, liczymy głębokość (rodzic jest już policzony). Przechodząc malejąco, sumujemy podwładnych (dzieci są już policzone). Obie pętle mają O(n).
Pod jakim indeksem tablicy (korzeń = 1) stanie liczba 7 po wstawieniu do pustego BST kolejno 10, 4, 15, 12, 2, 7?
Do pustego kopca wstawiamy 5, 3, 8, 1, 9. Jaka liczba jest w T[2]?
W jakiej kolejności inorder wypisuje BST?
- tak, jak wstawiano
- rosnąco
- poziomami od korzenia
Miejsce zerowe, pierwiastek, pole i Monte Carlo
Niektórych wartości nie da się policzyć dokładnie. Wtedy przybliżamy je krok po kroku, aż błąd będzie mniejszy od zadanej dokładności.
Połowienie przedziału (bisekcja)
Jeśli funkcja ciągła ma na końcach przedziału [a, b] różne znaki, to gdzieś w środku ma miejsce zerowe. Sprawdzamy znak w środku i zostawiamy tę połowę, w której zmienia się znak. Każdy krok połowi przedział.
def f(x):
return x**3 - x - 2
def bisekcja(a, b, eps): # f(a) i f(b) mają różne znaki
while b - a > eps:
s = (a + b) / 2
if f(a) * f(s) <= 0: # zmiana znaku w lewej połowie
b = s
else:
a = s
return (a + b) / 2
def pierwiastek(p, eps): # metoda Herona (Newtona) dla p > 0
x = p
while abs(x * x - p) > eps:
x = (x + p / x) / 2
return x#include <cmath>
using namespace std;
double f(double x) {
return x * x * x - x - 2;
}
double bisekcja(double a, double b, double eps) { // f(a) i f(b) mają różne znaki
while (b - a > eps) {
double s = (a + b) / 2;
if (f(a) * f(s) <= 0) b = s; // zmiana znaku w lewej połowie
else a = s;
}
return (a + b) / 2;
}
double pierwiastek(double p, double eps) { // metoda Herona (Newtona) dla p > 0
double x = p;
while (fabs(x * x - p) > eps)
x = (x + p / x) / 2;
return x;
}f(x) = x³ − x − 2
// f(a) i f(b) mają różne znaki
funkcja bisekcja(a, b, ε)
dopóki b − a > ε wykonuj
s ← (a + b) / 2
jeżeli f(a) · f(s) ≤ 0 // zmiana znaku w lewej połowie
b ← s
w przeciwnym razie
a ← s
zwróć (a + b) / 2
funkcja pierwiastek(p, ε) // metoda Herona, p > 0
x ← p
dopóki |x · x − p| > ε wykonuj
x ← (x + p / x) / 2
zwróć xMetoda Herona (Newtona) uśrednia x i p/x: jeśli x jest za duże, to p/x jest za małe, więc średnia leży bliżej √p. Zbiega bardzo szybko, zwykle w kilku krokach.
Pole pod wykresem
Dzielimy [a, b] na n pasków o szerokości h i sumujemy pola prostokątów albo trapezów. Im większe n, tym lepsze przybliżenie.
def g(x):
return x * x
def pole_prostokaty(a, b, n): # wysokość brana w środku każdego paska
h = (b - a) / n
s = 0
for i in range(n):
s += g(a + i * h + h / 2)
return s * h
def pole_trapezy(a, b, n):
h = (b - a) / n
s = (g(a) + g(b)) / 2
for i in range(1, n):
s += g(a + i * h)
return s * hdouble g(double x) {
return x * x;
}
double pole_prostokaty(double a, double b, int n) { // wysokość w środku paska
double h = (b - a) / n, s = 0;
for (int i = 0; i < n; i++)
s += g(a + i * h + h / 2);
return s * h;
}
double pole_trapezy(double a, double b, int n) {
double h = (b - a) / n, s = (g(a) + g(b)) / 2;
for (int i = 1; i < n; i++)
s += g(a + i * h);
return s * h;
}g(x) = x²
funkcja pole_prostokaty(a, b, n) // wysokość w środku paska
h ← (b − a) / n; s ← 0
dla i = 0, 1, …, n-1 wykonuj
s ← s + g(a + i · h + h / 2)
zwróć s · h
funkcja pole_trapezy(a, b, n)
h ← (b − a) / n
s ← (g(a) + g(b)) / 2
dla i = 1, 2, …, n-1 wykonuj
s ← s + g(a + i · h)
zwróć s · hMetoda Monte Carlo
Losujemy punkty w kwadracie i liczymy, jaka część trafiła do figury. Stosunek pól jest w przybliżeniu równy stosunkowi trafień. Dla ćwiartki koła o promieniu 1 to π/4.
import random
def pi_monte_carlo(n):
trafione = 0
for _ in range(n):
x, y = random.random(), random.random() # punkt w kwadracie 1×1
if x * x + y * y <= 1: # wewnątrz ćwiartki koła
trafione += 1
return 4 * trafione / n#include <random>
using namespace std;
double pi_monte_carlo(int n) {
mt19937 gen(2026); // generator z ustalonym ziarnem
uniform_real_distribution<double> los(0.0, 1.0);
int trafione = 0;
for (int i = 0; i < n; i++) {
double x = los(gen), y = los(gen); // punkt w kwadracie 1×1
if (x * x + y * y <= 1) trafione++; // wewnątrz ćwiartki koła
}
return 4.0 * trafione / n;
}funkcja pi_monte_carlo(n)
trafione ← 0
powtórz n razy
x ← losowa liczba z [0, 1)
y ← losowa liczba z [0, 1) // punkt w kwadracie 1×1
jeżeli x² + y² ≤ 1 // wewnątrz ćwiartki koła
trafione ← trafione + 1
zwróć 4 · trafione / nIle kroków bisekcji potrzeba, żeby przedział [0, 1] skurczył się do długości nie większej niż 0,001?
Wykonaj jeden krok metody Herona dla p = 10, zaczynając od x = 10. Jakie jest nowe x?
Jaki będzie wynik metody trapezów dla funkcji liniowej?
- Zawsze przybliżony
- Dokładny, bo trapezy idealnie pokrywają pole pod prostą
- Zależy od n
Geometria na liczbach całkowitych
Punkty o współrzędnych całkowitych pozwalają liczyć dokładnie, bez błędów zaokrągleń. Wystarczą dodawanie, mnożenie i porównanie.
def iloczyn(ax, ay, bx, by, cx, cy): # iloczyn wektorowy AB × AC
return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
# > 0: C na lewo od prostej AB, < 0: na prawo, = 0: A, B, C leżą na jednej prostej
def pole_trojkata(ax, ay, bx, by, cx, cy):
return abs(iloczyn(ax, ay, bx, by, cx, cy)) / 2
def mniejszy_ulamek(a, b, c, d): # czy a/b < c/d, gdy b > 0 i d > 0
return a * d < c * b # bez dzielenia: wynik dokładny
def pozycje(ruchy): # kolejne punkty po przesunięciach (dx, dy)
x = y = 0
wynik = []
for dx, dy in ruchy:
x += dx
y += dy
wynik.append((x, y))
return wynik#include <cstdlib>
#include <vector>
#include <utility>
using namespace std;
long long iloczyn(long long ax, long long ay, long long bx, long long by, long long cx, long long cy) {
return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); // iloczyn wektorowy AB × AC
}
// > 0: C na lewo od prostej AB, < 0: na prawo, = 0: A, B, C leżą na jednej prostej
double pole_trojkata(long long ax, long long ay, long long bx, long long by, long long cx, long long cy) {
return llabs(iloczyn(ax, ay, bx, by, cx, cy)) / 2.0;
}
bool mniejszy_ulamek(long long a, long long b, long long c, long long d) { // a/b < c/d, b, d > 0
return a * d < c * b; // bez dzielenia: wynik dokładny
}
vector<pair<long long, long long>> pozycje(const vector<pair<long long, long long>>& ruchy) {
long long x = 0, y = 0;
vector<pair<long long, long long>> wynik;
for (auto [dx, dy] : ruchy) {
x += dx;
y += dy;
wynik.push_back({x, y});
}
return wynik;
}funkcja iloczyn(A, B, C) // iloczyn wektorowy AB × AC
zwróć (xB − xA)·(yC − yA) − (yB − yA)·(xC − xA)
// > 0: C na lewo od AB; < 0: na prawo; = 0: współliniowe
funkcja pole_trojkata(A, B, C)
zwróć |iloczyn(A, B, C)| / 2
funkcja mniejszy_ulamek(a, b, c, d) // a/b < c/d przy b, d > 0
zwróć a · d < c · b
funkcja pozycje(ruchy)
x ← 0; y ← 0
dla każdego (dx, dy) w ruchy wykonuj
x ← x + dx; y ← y + dy
dopisz (x, y) do wynikuIloczyn wektorowy mówi, po której stronie prostej AB leży punkt C. Gdy jest równy 0, punkty leżą na jednej prostej. Jego wartość bezwzględna to podwojone pole trójkąta ABC.
Kąty, nachylenia i ułamki porównuj mnożeniem na krzyż zamiast dzielenia: a/b < c/d ⇔ a·d < c·b (przy dodatnich b i d). Odległości porównuj bez pierwiastka, przez kwadrat: dx² + dy².
Punkt (x, y) leży w prostokącie o rogach (x1, y1) i (x2, y2), gdy x1 ≤ x ≤ x2 i y1 ≤ y ≤ y2. Zwróć uwagę, czy brzeg się liczy: „wewnątrz” zwykle oznacza nierówności ostre.
Ile wynosi pole trójkąta o wierzchołkach (0, 0), (4, 0), (0, 3)?
Czy punkty (1, 1), (3, 2), (7, 4) leżą na jednej prostej?
- Tak
- Nie
Dron startuje z (0, 0) i wykonuje przesunięcia (2, 3), (4, −1), (1, −2). Podaj jego końcową pozycję jako dwie liczby.
Algorytm zachłanny i programowanie dynamiczne
Algorytm zachłanny w każdym kroku wybiera to, co w tej chwili wygląda najlepiej. Bywa szybki i poprawny, ale nie zawsze.
Przy wydawaniu reszty zachłannie bierzemy jak najwięcej największych monet. Dla polskich nominałów (1, 2, 5, 10, 20, 50 gr…) daje to zawsze najmniej monet. Dla nominałów 1, 3, 4 i kwoty 6 algorytm weźmie 4 + 1 + 1, czyli 3 monety, choć wystarczą 3 + 3.
Programowanie dynamiczne rozwiązuje problem dla wszystkich mniejszych kwot i zapisuje wyniki w tablicy. dp[k] to najmniejsza liczba monet na kwotę k: sprawdzamy każdą monetę m i bierzemy dp[k − m] + 1.
def reszta_zachlanna(kwota, nominaly): # nominały od największego
ile = 0
for m in nominaly:
ile += kwota // m
kwota %= m
return ile
def reszta_optymalna(kwota, nominaly):
NIESKONCZONOSC = kwota + 1
dp = [0] + [NIESKONCZONOSC] * kwota # dp[k]: najmniej monet na kwotę k
for k in range(1, kwota + 1):
for m in nominaly:
if m <= k and dp[k - m] + 1 < dp[k]:
dp[k] = dp[k - m] + 1
return dp[kwota]#include <vector>
using namespace std;
int reszta_zachlanna(int kwota, const vector<int>& nominaly) { // od największego
int ile = 0;
for (int m : nominaly) {
ile += kwota / m;
kwota %= m;
}
return ile;
}
int reszta_optymalna(int kwota, const vector<int>& nominaly) {
vector<int> dp(kwota + 1, kwota + 1); // dp[k]: najmniej monet na kwotę k
dp[0] = 0;
for (int k = 1; k <= kwota; k++)
for (int m : nominaly)
if (m <= k && dp[k - m] + 1 < dp[k])
dp[k] = dp[k - m] + 1;
return dp[kwota];
}// N[0..k-1] – nominały od największego
funkcja reszta_zachlanna(kwota, N)
ile ← 0
dla każdego m w N wykonuj
ile ← ile + kwota div m
kwota ← kwota mod m
zwróć ile
funkcja reszta_optymalna(kwota, N)
dp[0] ← 0
dp[1..kwota] ← ∞ // dp[k]: najmniej monet na kwotę k
dla k = 1, 2, …, kwota wykonuj
dla każdego m w N wykonuj
jeżeli m ≤ k oraz dp[k − m] + 1 < dp[k]
dp[k] ← dp[k − m] + 1
zwróć dp[kwota]| k | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| dp[k] dla 1, 3, 4 | 0 | 1 | 2 | 1 | 1 | 2 | 2 |
| Strategia | Kiedy | Przykłady |
|---|---|---|
| dziel i zwyciężaj | problem dzieli się na niezależne mniejsze części | scalanie, quicksort, szybkie potęgowanie, wyszukiwanie binarne |
| zachłanna | lokalnie najlepszy wybór prowadzi do optimum | reszta w polskich monetach, plan zajęć według najwcześniejszego końca |
| programowanie dynamiczne | podproblemy się powtarzają | reszta dla dowolnych nominałów, Fibonacci z tablicą, plecak |
Wybór przedziałów
Z listy przedziałów czasu (np. filmów, zajęć) wybieramy jak najwięcej takich, które na siebie nie nachodzą. Strategia zachłanna, która zawsze daje optimum: sortujemy po końcu i bierzemy każdy przedział, który zaczyna się nie wcześniej niż koniec ostatnio wybranego.
def najwiecej_przedzialow(P): # P: lista par (początek, koniec)
ile = 0
koniec = None
for p, k in sorted(P, key=lambda x: x[1]): # od najwcześniejszego końca
if koniec is None or p >= koniec:
ile += 1
koniec = k
return ile#include <vector>
#include <algorithm>
#include <utility>
using namespace std;
int najwiecej_przedzialow(vector<pair<int, int>> P) { // pary (początek, koniec)
sort(P.begin(), P.end(), [](auto a, auto b) { return a.second < b.second; });
int ile = 0;
bool pierwszy = true;
int koniec = 0;
for (auto [p, k] : P) // od najwcześniejszego końca
if (pierwszy || p >= koniec) {
ile++;
koniec = k;
pierwszy = false;
}
return ile;
}funkcja najwiecej_przedzialow(P) // pary (początek, koniec)
posortuj P rosnąco według końca
ile ← 0; koniec ← −∞
dla każdej pary (p, k) w P wykonuj
jeżeli p ≥ koniec
ile ← ile + 1
koniec ← k
zwróć ilePo czym sortować przedziały, żeby zachłanny wybór był optymalny?
- po początku
- po długości
- po końcu
Przedziały: 9–12, 11–16, 12–14, 15–17, 13–15. Ile najwięcej z nich można wybrać bez nakładania się (koniec jednego może być początkiem następnego)?
Ile monet wyda algorytm zachłanny dla kwoty 6 i nominałów 4, 3, 1?
Ile monet to optimum dla kwoty 6 i nominałów 4, 3, 1?
Kwota 30, nominały 25, 10, 1. Czy algorytm zachłanny da optimum?
- Tak
- Nie: zachłannie 25 + 5·1 = 6 monet, a 10 + 10 + 10 to 3 monety
Arkusz kalkulacyjny: formuły i adresowanie
W każdym arkuszu od 2023 r. jedno zadanie rozwiązuje się w arkuszu kalkulacyjnym: wczytujesz plik z danymi, dopisujesz kolumny z formułami, robisz zestawienie i wykres. Możesz użyć Excela albo LibreOffice Calc.
Wczytanie pliku
Adresy względne i bezwzględne
Formuła kopiowana w dół zmienia adresy: =C2*D2 w wierszu 3 staje się =C3*D3. Znak $ blokuje zmianę: $H$1 zostaje zawsze tą samą komórką, $A2 blokuje kolumnę, A$2 blokuje wiersz. Klawisz F4 przełącza te warianty.
=C2*D2 wartość sprzedaży w wierszu
=C2*$H$1 cena z jednej komórki, wspólnej dla wszystkich wierszy
=SUMA(E2:E366) suma kolumny
=ŚREDNIA(C2:C366)
=MAX(C2:C366) =MIN(C2:C366)Warunki
=JEŻELI(C2>=10;"duża";"mała")
=JEŻELI(ORAZ(C2>=5;D2<3);1;0) oba warunki
=JEŻELI(LUB(B2="bułka";B2="rogal");1;0) co najmniej jeden
=JEŻELI(C2<20;"mało";JEŻELI(C2<50;"średnio";"dużo")) zagnieżdżoneLiczenie i sumowanie z warunkiem
=LICZ.JEŻELI(B2:B500;"bułka") ile wierszy z bułką
=LICZ.JEŻELI(C2:C500;">=10") ile wierszy z ilością co najmniej 10
=SUMA.JEŻELI(B2:B500;"bułka";E2:E500) suma wartości dla bułek
=SUMA.WARUNKÓW(E2:E500;B2:B500;"bułka";F2:F500;3) bułki w marcu (F: miesiąc)
=LICZ.WARUNKI(B2:B500;"bułka";C2:C500;">=10")Tekst i wyszukiwanie
=LEWY(B2;3) =PRAWY(B2;2) =FRAGMENT.TEKSTU(B2;2;4) =DŁ(B2)
=B2&" "&C2 sklejenie tekstów
=WYSZUKAJ.PIONOWO(B2;$K$2:$L$20;2;FAŁSZ) cena produktu z tabeli K:L
=ZAOKR(E2;2) zaokrąglenie do 2 miejsc| Polski Excel | Angielski | Co robi |
|---|---|---|
| JEŻELI, ORAZ, LUB | IF, AND, OR | warunki |
| LICZ.JEŻELI, LICZ.WARUNKI | COUNTIF, COUNTIFS | liczenie z warunkiem |
| SUMA.JEŻELI, SUMA.WARUNKÓW | SUMIF, SUMIFS | sumowanie z warunkiem |
| ŚREDNIA.JEŻELI, MAKS.WARUNKÓW | AVERAGEIF, MAXIFS | średnia, maksimum z warunkiem |
| WYSZUKAJ.PIONOWO | VLOOKUP | wartość z tabeli według klucza |
| PODAJ.POZYCJĘ, INDEKS | MATCH, INDEX | pozycja wartości, wartość z pozycji |
| LEWY, PRAWY, FRAGMENT.TEKSTU, DŁ | LEFT, RIGHT, MID, LEN | tekst |
| ZAOKR | ROUND | zaokrąglenie |
0,5. W LibreOffice nazwy funkcji są w większości takie same jak w Excelu.Formułę =A1*$B$1 z komórki C1 skopiowano do D3. Jak wygląda formuła w D3?
W A1:A5 są liczby 1, 4, 3, 7, 5. Co zwróci =LICZ.JEŻELI(A1:A5;">3")?
Co zwróci =FRAGMENT.TEKSTU("informatyka";3;5)?
Co zwróci =JEŻELI(ORAZ(5>3;2>4);"tak";"nie")?
- tak
- nie
- błąd
Arkusz: daty, zestawienia i wykresy
Większość zadań z arkusza to zestawienia: suma, średnia albo maksimum dla każdego miesiąca, klienta czy rodzaju. Najszybciej robi się je tabelą przestawną, a do niej często trzeba dorobić wykres.
Daty
Arkusz przechowuje datę jako liczbę dni, a jej wygląd to tylko format komórki. Dlatego daty można odejmować i porównywać. Gdy zamiast daty widzisz liczbę, np. 45658 (to 1 stycznia 2025), zmień format komórki na datę.
=ROK(A2) =MIESIĄC(A2) =DZIEŃ(A2)
=DZIEŃ.TYG(A2;2) 1 = poniedziałek, …, 7 = niedziela
=TEKST(A2;"mmmm") nazwa miesiąca
=A3-A2 liczba dni między datami
=DATA(2025;3;1) data z roku, miesiąca i dnia
=DATA(PRAWY(A2;4);FRAGMENT.TEKSTU(A2;4;2);LEWY(A2;2)) tekst "dd.mm.rrrr" na datęTabela przestawna
Najczęstsza wartość w każdej grupie, np. najlepiej sprzedająca się odmiana w każdym miesiącu: w tabeli przestawnej ustaw miesiące w wierszach, odmiany w kolumnach i sumę w wartościach. Potem dla każdego wiersza znajdź maksimum i nazwę jego kolumny:
=INDEKS($B$4:$K$4;PODAJ.POZYCJĘ(MAX(B5:K5);B5:K5;0))Wykres
Zaznacz zestawienie razem z nagłówkami i wybierz Wstawianie → Wykres kolumnowy (albo typ podany w treści). Punkty za wykres zależą od opisu, więc odhacz każdą rzecz:
Ile dni minęło od 1 marca 2025 do 15 kwietnia 2025 (wynik =A3-A2)?
Co zwróci =DZIEŃ.TYG(A2;2), gdy w A2 jest 4 maja 2026 (poniedziałek)?
W kolumnie z datami widać liczby typu 45658. Co zrobić?
- Zaimportować plik jeszcze raz
- Zmienić format komórek na datę
- Podzielić przez 365
Symulacja dzień po dniu
Typowe zadanie: stan czegoś (wody, towaru, minerału) zmienia się każdego dnia według reguł z treści. W arkuszu każdy wiersz to jeden dzień, a stan na początku dnia to stan z końca poprzedniego wiersza.
Przykład: zbiornik na deszczówkę ma pojemność zapisaną w komórce M1. Na początku jest w nim 100 l. Każdy milimetr deszczu dolewa 10 l, a nadmiar ponad pojemność się przelewa. Potem, jeśli jest co najmniej 30 l, ogród podlewa się 30 litrami, a jeśli nie, tego dnia brakuje wody.
| Kolumna | Znaczenie | Wiersz 2 (pierwszy dzień) | Wiersz 3 i dalej |
|---|---|---|---|
| A | data | z pliku | z pliku |
| B | opady (mm) | z pliku | z pliku |
| C | stan rano | 100 | =F2 |
| D | po deszczu | =MIN($M$1;C2+10*B2) | kopiuj w dół |
| E | podlewanie | =JEŻELI(D2>=30;30;0) | kopiuj w dół |
| F | stan wieczorem | =D2-E2 | kopiuj w dół |
| G | brak wody (1/0) | =JEŻELI(D2<30;1;0) | kopiuj w dół |
| H | pełny (1/0) | =JEŻELI(D2=$M$1;1;0) | kopiuj w dół |
=SUMA(G2:G366) ile dni zabrakło wody
=PODAJ.POZYCJĘ(1;H2:H366;0) numer dnia, w którym zbiornik pierwszy raz był pełny
=INDEKS(A2:A366;PODAJ.POZYCJĘ(1;H2:H366;0)) data tego dniaPytanie typu „jaka najmniejsza pojemność wystarczy, żeby ani razu nie zabrakło wody” rozwiązujesz, zmieniając liczbę w M1 i patrząc na sumę braków. Gdy wartości jest dużo, szybciej jest napisać program:
def symuluj(opady, pojemnosc, start=100): # opady w mm, dzień po dniu
stan = start # litry w zbiorniku
braki = 0
pierwszy_pelny = None
for dzien, mm in enumerate(opady, start=1):
stan = min(pojemnosc, stan + 10 * mm) # deszcz; nadmiar się przelewa
if stan == pojemnosc and pierwszy_pelny is None:
pierwszy_pelny = dzien
if stan >= 30:
stan -= 30 # podlewanie
else:
braki += 1 # za mało wody tego dnia
return braki, pierwszy_pelny
def najmniejsza_pojemnosc(opady): # pierwsza pojemność bez braków wody
for pojemnosc in range(100, 10001, 10):
if symuluj(opady, pojemnosc)[0] == 0:
return pojemnosc
return None#include <vector>
#include <algorithm>
using namespace std;
// opady w mm, dzień po dniu; ustawia pierwszy_pelny (0, gdy zbiornik nigdy nie był pełny)
int symuluj(const vector<int>& opady, int pojemnosc, int& pierwszy_pelny, int start = 100) {
int stan = start, braki = 0; // litry w zbiorniku
pierwszy_pelny = 0;
for (int d = 0; d < (int)opady.size(); d++) {
stan = min(pojemnosc, stan + 10 * opady[d]); // deszcz; nadmiar się przelewa
if (stan == pojemnosc && pierwszy_pelny == 0)
pierwszy_pelny = d + 1;
if (stan >= 30) stan -= 30; // podlewanie
else braki++; // za mało wody tego dnia
}
return braki;
}
int najmniejsza_pojemnosc(const vector<int>& opady) { // pierwsza bez braków wody
int p;
for (int pojemnosc = 100; pojemnosc <= 10000; pojemnosc += 10)
if (symuluj(opady, pojemnosc, p) == 0) return pojemnosc;
return -1;
}funkcja symuluj(opady[1..n], pojemnosc)
stan ← 100; braki ← 0; pierwszy_pelny ← brak
dla d = 1, 2, …, n wykonuj
stan ← min(pojemnosc, stan + 10 · opady[d]) // nadmiar się przelewa
jeżeli stan = pojemnosc oraz pierwszy_pelny = brak
pierwszy_pelny ← d
jeżeli stan ≥ 30
stan ← stan − 30 // podlewanie
w przeciwnym razie
braki ← braki + 1
zwróć braki, pierwszy_pelny
funkcja najmniejsza_pojemnosc(opady)
dla pojemnosc = 100, 110, 120, …, 10000 wykonuj
jeżeli symuluj(opady, pojemnosc) daje braki = 0
zwróć pojemnosc
zwróć brakSuma narastająco
Gdy reguła zależy od tego, ile było dotąd (np. rabat od łącznych zakupów klienta), potrzebna jest suma od początku do bieżącego wiersza. Pierwszy adres jest zablokowany, drugi przesuwa się przy kopiowaniu:
=SUMA($C$2:C2) narastająco dla wszystkich wierszy
=SUMA.JEŻELI($B$2:B2;B2;$C$2:C2) narastająco tylko dla klienta z B2Pojemność 150 l, opady przez 5 dni: 0, 0, 0, 4, 0 mm. Ile litrów jest w zbiorniku wieczorem 3. dnia?
Dla tych samych danych: ile dni zabrakło wody?
Opady przez 7 dni: 20, 0, 0, 0, 0, 0, 0 mm. Jaka jest najmniejsza pojemność (sprawdzana co 10 l od 100 l), przy której ani razu nie zabraknie wody?
SQL: zapytania do jednej tabeli
Zadanie z bazą danych daje kilka plików z tabelami. Importujesz je do Accessa, LibreOffice Base albo MySQL i odpowiadasz na pytania zapytaniami. Jedno podzadanie zwykle wymaga napisania zapytania SQL na papierze.
W tej i następnej lekcji używamy małej bazy szkolnej biblioteki:
uczniowie
| id_ucznia | imie | nazwisko | klasa |
|---|---|---|---|
| 1 | Ola | Nowak | 3A |
| 2 | Jan | Kowalski | 3A |
| 3 | Ewa | Wiśniewska | 3B |
| 4 | Piotr | Zając | 3B |
| 5 | Kasia | Lis | 2A |
ksiazki
| id_ksiazki | tytul | autor | gatunek | rok_wydania |
|---|---|---|---|---|
| 1 | Wiedźmin | Sapkowski | fantastyka | 1993 |
| 2 | Solaris | Lem | fantastyka | 1961 |
| 3 | Lalka | Prus | powieść | 1890 |
| 4 | Quo vadis | Sienkiewicz | powieść | 1896 |
| 5 | Hobbit | Tolkien | fantastyka | 1937 |
| 6 | Pan Tadeusz | Mickiewicz | poezja | 1834 |
wypozyczenia
| id | id_ucznia | id_ksiazki | data_wyp | data_zwrotu |
|---|---|---|---|---|
| 1 | 1 | 1 | 2025-09-02 | 2025-09-16 |
| 2 | 1 | 2 | 2025-09-20 | 2025-10-01 |
| 3 | 2 | 3 | 2025-09-05 | 2025-09-30 |
| 4 | 3 | 1 | 2025-10-03 | 2025-10-20 |
| 5 | 3 | 5 | 2025-10-21 | |
| 6 | 1 | 5 | 2025-10-02 | 2025-10-15 |
| 7 | 4 | 6 | 2025-11-04 | 2025-11-18 |
| 8 | 2 | 1 | 2025-11-10 | |
| 9 | 1 | 4 | 2025-11-12 | 2025-11-30 |
| 10 | 3 | 2 | 2025-11-20 | 2025-12-01 |
Wybieranie i warunki
SELECT tytul, rok_wydania
FROM ksiazki
WHERE gatunek = 'fantastyka'
ORDER BY rok_wydania;| tytul | rok_wydania |
|---|---|
| Hobbit | 1937 |
| Solaris | 1961 |
| Wiedźmin | 1993 |
SELECT tytul
FROM ksiazki
WHERE autor LIKE 'S%' AND rok_wydania BETWEEN 1850 AND 2000;| tytul |
|---|
| Wiedźmin |
| Quo vadis |
LIKE 'S%' to tekst zaczynający się od S (% to dowolny ciąg znaków, _ to jeden znak). BETWEEN obejmuje oba końce. Brak wartości sprawdza się przez IS NULL, nigdy przez = NULL.
SELECT COUNT(*)
FROM wypozyczenia
WHERE data_zwrotu IS NULL;| COUNT(*) |
|---|
| 2 |
Grupowanie
GROUP BY zbiera wiersze o tej samej wartości w jedną grupę. Funkcje COUNT, SUM, AVG, MIN i MAX liczą wtedy wynik dla każdej grupy osobno. WHERE filtruje wiersze przed grupowaniem, a HAVING filtruje gotowe grupy.
SELECT gatunek, COUNT(*) AS ile
FROM ksiazki
GROUP BY gatunek
ORDER BY ile DESC;| gatunek | ile |
|---|---|
| fantastyka | 3 |
| powieść | 2 |
| poezja | 1 |
SELECT gatunek, MIN(rok_wydania) AS najstarsza
FROM ksiazki
GROUP BY gatunek
HAVING COUNT(*) >= 2;| gatunek | najstarsza |
|---|---|
| fantastyka | 1937 |
| powieść | 1890 |
SELECT id_ksiazki, COUNT(*) AS ile
FROM wypozyczenia
GROUP BY id_ksiazki
ORDER BY ile DESC
LIMIT 1;| id_ksiazki | ile |
|---|---|
| 1 | 3 |
LIMIT 1 na końcu pisze się SELECT TOP 1 na początku. W LIKE zamiast % i _ są * i ?. Daty zapisuje się jako #2025-09-01#. Na papierze wystarczy standardowy SQL.Ile wierszy zwróci SELECT * FROM ksiazki WHERE rok_wydania < 1900;?
Jaki wynik da SELECT AVG(rok_wydania) FROM ksiazki WHERE gatunek = 'powieść';?
Chcesz wypisać tylko te gatunki, które mają co najmniej 2 książki. Gdzie wpisać warunek COUNT(*) >= 2?
- w WHERE
- w HAVING
- w ORDER BY
SQL: łączenie tabel, import i podzapytania
Dane z jednego pytania prawie zawsze leżą w kilku tabelach. Łączy się je przez klucze: klucz główny jednoznacznie wskazuje wiersz, a klucz obcy w innej tabeli wskazuje na ten wiersz.
W naszej bazie wypozyczenia.id_ucznia to klucz obcy wskazujący uczniowie.id_ucznia. Jeden uczeń ma wiele wypożyczeń, więc to relacja jeden do wielu.
Import plików
JOIN
SELECT u.imie, u.nazwisko, COUNT(*) AS ile
FROM uczniowie AS u
JOIN wypozyczenia AS w ON w.id_ucznia = u.id_ucznia
GROUP BY u.id_ucznia, u.imie, u.nazwisko
ORDER BY ile DESC;| imie | nazwisko | ile |
|---|---|---|
| Ola | Nowak | 4 |
| Ewa | Wiśniewska | 3 |
| Jan | Kowalski | 2 |
| Piotr | Zając | 1 |
W GROUP BY podajemy identyfikator, a nie tylko imię i nazwisko, bo dwie osoby mogą się tak samo nazywać. Kolejne tabele dołącza się kolejnymi JOIN:
SELECT DISTINCT k.tytul
FROM ksiazki AS k
JOIN wypozyczenia AS w ON w.id_ksiazki = k.id_ksiazki
JOIN uczniowie AS u ON u.id_ucznia = w.id_ucznia
WHERE u.klasa = '3B'
ORDER BY k.tytul;| tytul |
|---|
| Hobbit |
| Pan Tadeusz |
| Solaris |
| Wiedźmin |
Kto nie ma żadnego wiersza
LEFT JOIN zostawia wszystkie wiersze z lewej tabeli. Gdy nie ma pary, kolumny z prawej mają NULL. To sposób na pytania „którzy klienci nic nie kupili”:
SELECT u.imie, u.nazwisko
FROM uczniowie AS u
LEFT JOIN wypozyczenia AS w ON w.id_ucznia = u.id_ucznia
WHERE w.id IS NULL;| imie | nazwisko |
|---|---|
| Kasia | Lis |
To samo można zapisać podzapytaniem z NOT IN:
SELECT tytul
FROM ksiazki
WHERE id_ksiazki NOT IN (SELECT id_ksiazki
FROM wypozyczenia
WHERE data_zwrotu IS NULL)
ORDER BY tytul;| tytul |
|---|
| Lalka |
| Pan Tadeusz |
| Quo vadis |
| Solaris |
Daty i liczenie różnych wartości
SELECT MONTH(data_wyp) AS miesiac, COUNT(*) AS ile
FROM wypozyczenia
GROUP BY MONTH(data_wyp)
ORDER BY miesiac;| miesiac | ile |
|---|---|
| 9 | 3 |
| 10 | 3 |
| 11 | 4 |
MONTH i YEAR działają w MySQL. W Accessie piszemy Month(data_wyp), a w LibreOffice Base MONTH("data_wyp"). Liczba różnych wartości to COUNT(DISTINCT …):
SELECT COUNT(DISTINCT w.id_ucznia)
FROM wypozyczenia AS w
JOIN ksiazki AS k ON k.id_ksiazki = w.id_ksiazki
WHERE k.gatunek = 'fantastyka';| COUNT(DISTINCT w.id_ucznia) |
|---|
| 3 |
Access nie ma COUNT(DISTINCT). Robi się to podzapytaniem, które najpierw wybiera różne wartości, a potem je zlicza:
SELECT COUNT(*)
FROM (SELECT DISTINCT w.id_ucznia
FROM wypozyczenia AS w
JOIN ksiazki AS k ON k.id_ksiazki = w.id_ksiazki
WHERE k.gatunek = 'fantastyka') AS t;Nowa tabela na papierze
Ostatnie podzadanie zwykle dokłada do bazy nową tabelę i prosi o zapytanie, które z niej korzysta. Czasem trzeba też podać jej definicję:
CREATE TABLE oceny (
id_oceny INTEGER PRIMARY KEY,
id_ucznia INTEGER REFERENCES uczniowie(id_ucznia),
id_ksiazki INTEGER REFERENCES ksiazki(id_ksiazki),
ocena INTEGER
);Ile wierszy zwróci SELECT * FROM uczniowie AS u JOIN wypozyczenia AS w ON w.id_ucznia = u.id_ucznia;?
A ile wierszy, jeśli zamiast JOIN użyjesz LEFT JOIN?
Ile łącznie wypożyczeń mają uczniowie klasy 3A?
Co było na maturze i gdzie to jest w kursie
Przegląd zadań z algorytmów, programowania, arkusza kalkulacyjnego i baz danych z arkuszy rozszerzonych z lat 2015–2026. Testy prawda/fałsz z wiedzy ogólnej (sieci, grafika, systemy) nie są tu opisane.
| Rok | Zadanie | Co trzeba było umieć | Lekcja |
|---|---|---|---|
| 2015 | 1. Problem telewidza | zachłanny wybór nienachodzących przedziałów | Strategie |
| 2015 | 3. Rozszerzony algorytm Euklidesa | NWD jako a·x + b·y | NWD i NWW |
| 2016 | 1. Liczby skojarzone | suma dzielników do √n | Cyfry i dzielniki |
| 2016 | 2. Przestawienia w tablicy | podział tablicy względem klucza | Sortowanie szybkie |
| 2017 | 1. Prostokąt | największy iloczyn z warunkiem podzielności | Wyszukiwanie liniowe |
| 2017 | 2. Rekurencja | funkcja rekurencyjna po bitach liczby | Zapis binarny |
| 2018 | 1. Analiza algorytmu | wyszukiwanie binarne pierwiastka | Wyszukiwanie binarne |
| 2018 | 2. Krajobraz | porównywanie ułamków, porządek | Geometria |
| 2019 | 1. Ulubione liczby | binarne szukanie granicy, złożoność | Wyszukiwanie binarne |
| 2019 | 2. Analiza algorytmu | drzewo wywołań, generowanie napisów | Generowanie ciągów |
| 2020 | 1. Podobne tablice | przesunięcie cykliczne tablicy | Wyszukiwanie liniowe |
| 2020 | 2. Symetryczny ciąg | śledzenie rekurencji, długość wyniku | Rekurencja |
| 2021 | 1. Cyfrowe dopełnienie | przekształcanie cyfr liczby | Budowanie liczb z cyfr |
| 2021 | 2. Analiza algorytmu | wstawianie do kopca | Drzewa |
| 2022 | 1. n-permutacja | zliczanie wystąpień w tablicy liczników | Zliczanie |
| 2022 | 2. ab-słowo | liczniki z przodu i z tyłu (prefiksy) | Sumy prefiksowe |
| 2023 | 1. Biblioteczka Adama | drzewo BST w tablicy, przejście inorder | Drzewa |
| 2023 | 2. Liczby binarne | bloki bitów, XOR, porównywanie binarnych | Zapis binarny |
| 2023 | 3. Liczba Pi | zliczanie fragmentów, najdłuższy fragment o zadanej własności | Sumy prefiksowe, Wyszukiwanie liniowe |
| 2023 | 6. Konfitury owocowe | zestawienie miesięczne, wykres, produkcja dzień po dniu | Arkusz: daty i wykresy, Arkusz: symulacja |
| 2023 | 7. Gry planszowe | liczenie i średnie w grupach, łączenie tabel, kategorie wieku | SQL: jedna tabela, SQL: łączenie tabel |
| 2024 | 1. Plansza | osiągalność na planszy (DP) | Plansze 2D |
| 2024 | 2. Cyfry; 3. Nieparzysty skrót | budowanie liczby z cyfr, NWD | Budowanie liczb z cyfr |
| 2024 | 4. Liczby | podzielność, rozkład na czynniki, fragment o największej średniej | Liczby pierwsze, Sumy prefiksowe |
| 2024 | 7. Hurtownia | sumy dla klientów, najpopularniejsza odmiana w miesiącu, rabat od sumy narastającej | Arkusz: formuły, Arkusz: daty i wykresy, Arkusz: symulacja |
| 2024 | 8. Rejestr wykroczeń | sumy z łączeniem tabel, miesiące, kierowcy bez wykroczeń | SQL: łączenie tabel |
| 2025 | 1. Funkcja rekurencyjna | rekurencja po parach cyfr, wersja iteracyjna | Budowanie liczb z cyfr |
| 2025 | 2. Zapis symboliczny | palindromy, kwadraty 3×3, system trójkowy | Napisy, Plansze 2D, Systemy |
| 2025 | 3. Dron | NWD, kolejne pozycje, punkty w kwadracie | Geometria |
| 2025 | 6. Martianeum | symulacja dzień po dniu z progami | Arkusz: symulacja |
| 2025 | 7. Woda na Marsie | łączenie tabel, daty, obszary bez pomiarów | SQL: łączenie tabel |
| 2026 | 1. Rekurencja | liczba wywołań, rekurencja jak w szybkim potęgowaniu | Szybkie potęgowanie |
| 2026 | 2. Dodawanie | przeniesienia przy dodawaniu pisemnym | Budowanie liczb z cyfr |
| 2026 | 3. Pary słów | kody ASCII, wspólne litery, początek i koniec słowa | Napisy |
| 2026 | 4. Korporacja | hierarchia z tablicą przełożonych | Drzewa |
| 2026 | 7. Staw | średnie miesięczne, wykres, symulacja wzrostu, najmniejszy parametr | Arkusz: daty i wykresy, Arkusz: symulacja |
| 2026 | 8. Sieć sklepów | liczba transakcji, klienci bez zakupów, różne sklepy w miesiącu | SQL: łączenie tabel |
Od 2023 r. w każdym arkuszu jest zadanie z arkusza kalkulacyjnego (zestawienia, wykresy, symulacja dzień po dniu) i zadanie z bazy danych (zapytania SQL, jedno pisane na papierze).
Arkusze z kluczem odpowiedzi i plikami z danymi znajdziesz na stronie CKE i na arkusze.pl.
Ściąga i plan na egzamin
Zestawienie wszystkich algorytmów z kursu i lista rzeczy, które warto mieć opanowane przed egzaminem.
| Algorytm | Złożoność | Kluczowy pomysł |
|---|---|---|
| suma cyfr, odwracanie | O(liczba cyfr) | % 10 i // 10 |
| dzielniki, test pierwszości | O(√n) | pary dzielników, d·d ≤ n |
| sito Eratostenesa | O(n log log n) | wykreślanie od i·i |
| NWD (Euklides) | O(log n) | (a, b) → (b, a % b) |
| zamiana systemów | O(liczba cyfr) | dzielenie przez p / schemat Hornera |
| szybkie potęgowanie | O(log n) | aⁿ = (aⁿᐟ²)² |
| Fibonacci iteracyjnie | O(n) | dwie ostatnie wartości |
| min i max naraz | ≈ 3n/2 porównań | porównania parami |
| wyszukiwanie binarne | O(log n) | połowienie, dane posortowane |
| bąbelkowe, wybór, wstawianie | O(n²) | zamiany sąsiadów / minimum / wstawianie |
| scalanie | O(n log n) | podziel, posortuj, scal |
| quicksort | średnio O(n log n), najgorzej O(n²) | podział względem pivota |
| zliczanie | O(n + k) | tablica liczników |
| wzorzec naiwnie | O(n·m) | porównanie od każdej pozycji |
| bisekcja | O(log((b − a)/ε)) | zmiana znaku |
| wydawanie reszty (DP) | O(kwota · liczba nominałów) | dp[k] = min(dp[k − m] + 1) |
| budowanie liczby z cyfr | O(liczba cyfr) | mnożnik 1, 10, 100… rośnie tylko przy dopisaniu |
| rozszerzony Euklides | O(log n) | x = y′, y = x′ − (a div b)·y′ |
| bloki, jedynki w zapisie binarnym | O(log n) | n % 2, n // 2, &, ^, >> |
| wszystkie napisy / podzbiory / permutacje | kⁿ / 2ⁿ / n! | rekurencja, maska bitowa, zamiany |
| sumy prefiksowe | O(n) budowa, O(1) zapytanie | suma t[a..b] = P[b+1] − P[a] |
| największa suma fragmentu (Kadane) | O(n) | przedłuż albo zacznij od nowa |
| szukanie po wyniku (pierwiastek) | O(log n) | warunek zmienia się raz |
| plansza: osiągalność / BFS | O(n·m) | P[i][j] z góry lub z lewej / kolejka |
| BST i kopiec w tablicy | O(wysokość) | dzieci 2i i 2i+1, rodzic i div 2 |
| hierarchia z tablicą przełożonych | O(n) | rosnąco głębokość, malejąco podwładni |
| iloczyn wektorowy | O(1) | strona prostej, współliniowość, pole |
| wybór przedziałów | O(n log n) | sortuj po końcu |
Dzień egzaminu
Najlepszy trening to arkusze z poprzednich lat ze strony CKE. Rozwiązuj je przy komputerze i z zegarem.