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ń.
Omówienie Zadaj pytanie
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.
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 Dodatkowe materiały
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!
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.
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!
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 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!
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.
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
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.
Omówienie Zadaj pytanie
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
Zadania na rozgrzewkę. Pozwolą zapoznać Ci się konceptem i przygotują cię do kolejnych zadań.
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!
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
Omówienie Zadaj pytanie
poziom OIJ 2
Zadaj pytanie