Dołącz do konkursu i wysyłaj zadania: https://szkopul.edu.pl/c/oki-olimpiada-od-podstaw
Sumy prefiksowe służą do szybkiego liczenia sumy dowolnego przedziału tablicy.
Aby policzyć sumę na przedziale $[l, r]$, możemy policzyć sumę na przedziale $[1,r]$ i odjąć od niej sumę na przedziale $[1,l-1]$. Wszystkie sumy postaci $[1,i]$ możemy policzyć na początku programu, przez co zapytanie będzie działać w czasie $\mathcal{O}(1)$.
Tablica
Tekstowe streszczenie sum prefiksowych
pref[i] = a[1]+...+a[i] liczona raz w O(n) pozwala odpowiadać na pytanie o sumę dowolnego przedziału [l,r] jedną operacją: pref[r] - pref[l-1]. Zamiast miliona operacji na zapytanie, jedna.
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
poziom OIJ 1
poziom OIJ 1
Dodatkowe: Rewolucja Ciapongowa 1
Wyślij
Zadania wymagające, czasami wręcz ostentacyjnie nietrywialnej, ilości pomyślunku, której skala pozostaje niewspółmierna do liczby linijek kodu prowadzących do rozwiązania.
poziom OIJ 1
poziom OIJ 1
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
poziom OIJ 1
Binary search (wyszukiwanie binarne) służy do szybkiego znajdowania elementu lub odpowiedzi w posortowanej tablicy.
W każdym kroku sprawdzamy środkowy element przedziału. Jeśli szukana wartość jest mniejsza, przechodzimy do lewej połowy, a jeśli większa - do prawej. Dzięki temu w każdej iteracji odrzucamy połowę możliwych odpowiedzi, więc algorytm działa w czasie $\mathcal{O}(log n)$.
Porównanie wyszukiwania binarnego z liniowym
Sprawdź w środku, odrzuć połowę!
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int N, Q;
cin >> N >> Q;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
while (Q--) {
int K;
cin >> K;
if (a[N - 1] < K) {
cout << "-1";
continue;
}
int l = 0;
int r = N - 1;
while (l < r) {
int s = (l + r) / 2;
if (a[s] < K) l = s + 1;
else r = s;
}
cout << a[l] << "";
}
}
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Posortowana tablica; pamiętaj żeby po wczytaniu danych zrobić sort(a.begin(), a.end()); lub inaczej posortować
vector<int> a = {2, 5, 7, 10, 14, 18, 21, 30};
int x = 14; // Szukana wartość
int l = 0;
int r = a.size() - 1;
while (l <= r) {
// Obliczamy środek przedziału
int mid = l + (r - l) / 2;
if (a[mid] == x) {
cout << "Znalazlem x na pozycji: " << mid;
return 0;
}
// Szukana wartość jest po prawej stronie
if (a[mid] < x) {
l = mid + 1;
}
// Szukana wartość jest po lewej stronie
else {
r = mid - 1;
}
}
// Jeśli pętla się zakończyła, element nie istnieje
cout << "Nie znalazlem x :<";
return 0;
}
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
poziom OIJ 3
Gąsienica (lub po angielsku two pointers) służy do przeglądania przedziałów tablicy w czasie liniowym.
Utrzymujemy dwa wskaźniki oznaczające początek i koniec aktualnego przedziału. Rozszerzamy przedział, gdy spełnia warunek, a w razie potrzeby przesuwamy jego początek. Dzięki temu każdy element jest odwiedzany najwyżej kilka razy, więc algorytm działa w czasie $\mathcal{O}(n)$.
Two pointers na usaco.guide
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int n;
long long S;
cin >> n >> S;
vector<long long> a(n);
for (int i = 0; i < n; ++i) cin >> x;
long long suma = 0;
int l = 0, best = 0;
for (int r = 0; r < n; ++r) {
suma += a[r]; // <------------------- rozszerzamy okno w prawo
while (suma > S) { // <-------------- dociagamy lewy koniec, az warunek wroci
suma -= a[l];
++l;
}
best = max(best, r - l + 1); // <---- teraz [l, r] jest poprawne
}
cout << best << "
";
return 0;
}
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
poziom OIJ 1
Zadania wymagające, czasami wręcz ostentacyjnie nietrywialnej, ilości pomyślunku, której skala pozostaje niewspółmierna do liczby linijek kodu prowadzących do rozwiązania.
poziom OIJ 2
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
Sito Eratostenesa służy do szybkiego znalezienia wszystkich liczb pierwszych z przedziału $[2, n]$.
sido era-to-ste-nesa
Zamiast sprawdzać każdą liczbę osobno,
idziemy od $2$ w górę i wykreślamy wielokrotności
każdej napotkanej liczby pierwszej.
To, co nie zostało wykreślone, jest pierwsze (bo nie jest wielokrotnością).
Wykreślanie dla liczby $i$ możemy zacząć od
$i \cdot i$, bo mniejsze wielokrotności wykreśliły
już mniejsze liczby pierwsze.
Całość działa w czasie $\mathcal{O}(n \log \log n)$, czyli niemal liniowo.
#include <bits/stdc++.h>
using namespace std;
int main(){
int n = 1000000;
vector<bool> pierwsza(n + 1, true); // false = przekreśliliśmy, nie pierwsza, true = jest ok
pierwsza[0] = pierwsza[1] = false;
for(long long i = 2; i * i <= n; i++){
if(pierwsza[i]){
// wykreślamy od i*i — mniejsze wielokrotności już wykreślone
for(long long j = i * i; j <= n; j += i)
pierwsza[j] = false;
}
}
// teraz pierwsza[x] mówi w O(1), czy x jest pierwsze
cout << (pierwsza[997] ? "997 jest pierwsze" : "997 jest złożone");
return 0;
}
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
Zadania wymagające, czasami wręcz ostentacyjnie nietrywialnej, ilości pomyślunku, której skala pozostaje niewspółmierna do liczby linijek kodu prowadzących do rozwiązania.
poziom OIJ 2
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
poziom OIJ 1
W wielu zadaniach możemy przyjąć strategię "divide and conquer", czyli podzielić problem na mniejsze problemy, które łatwiej rozwiązać.Zamiast sprawdzać wykładniczo wiele kombinacji, wypełniamy tablicę $dp$, w której $dp[i]$ oznacza najlepszą odpowiedź dla "mniejszej wersji" zadania. Każdy stan liczymy raz, więc typowa złożoność to $\mathcal{O}(n)$ lub $\mathcal{O}(n \cdot w)$. W odróżnieniu od zachłana, dynamik rozważa wszystkie możliwości w uporządkowany sposób.
Programowanie dynamiczne (dynamiki) polega na rozbiciu problemu na stany i liczeniu wyniku dla każdego stanu z wyników wcześniejszych, mniejszych stanów.
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 3
Zadania wymagające, czasami wręcz ostentacyjnie nietrywialnej, ilości pomyślunku, której skala pozostaje niewspółmierna do liczby linijek kodu prowadzących do rozwiązania.
Binary Search po wyniku stosujemy, gdy nie umiemy policzyć odpowiedzi wprost, ale umiemy szybko odpowiedzieć na pytanie: „czy da się osiągnąć wynik $x$?”.
Jeśli odpowiedzi układają się monotonicznie (TAK, TAK, ..., TAK, NIE, ..., NIE),
to szukamy binary searchem ostatniego TAK, czyli najlepszego osiągalnego wyniku.
Zamiast sprawdzać wszystkie wartości, wykonujemy $\mathcal{O}(\log(\text{zakres}))$ sprawdzeń.
To ulepszenie bruta: z milionów operacji robi się kilkadziesiąt.
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
poziom OIJ 3
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!
poziom OI 1
Zadania z materiału do tego tematu włącznie. Niekoniecznie muszą zawierać materiały z tego tematu, ale zalecamy zrobienie wszystkich dla powtórzenia i treningu!