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 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.
Tekstowe streszczenie sum prefiksowych new tab Sumy prefiksowe - Algorytmy dla początkujących #1 new tab Introduction to Prefix Sums na usaco.guide new tab
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
poziom OIJ 1
Omówienie Zadaj pytanie
Omówienie Zadaj pytanie
poziom OIJ 1
Omówienie Zadaj pytanie
poziom OIJ 1
Zadaj pytanie
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.
Zadaj pytanie
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!
Omówienie Zadaj pytanie

Dodatkowe: Bity

Wyślij
poziom OIJ 2
Zadaj pytanie
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 new tab Artykuł na geeksforgeeks.org new tab Binary search na usaco.guide new tab Link do kursu Codeforces EDU z lekcji new tab Omówienie z zajęć OKI new tab

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ń.
Omówienie
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!
Omówienie Zadaj pytanie
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 new tab Gąsienica na algorytmy.oki.org.pl new tab Krótkie i ciekawe zadania na LeetCode.com new tab
    #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
Zadaj pytanie
Zadaj pytanie
Zadaj pytanie
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
Omówienie Zadaj pytanie
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!
Zadaj pytanie
Sito Eratostenesa służy do szybkiego znalezienia wszystkich liczb pierwszych z przedziału $[2, n]$.
sido era-to-ste-nesa new tab
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ń.
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!
Zadaj pytanie
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.
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ń.
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!
Zadaj pytanie