├── static ├── .nojekyll ├── CNAME └── img │ ├── Scc-1.png │ ├── dag1.png │ ├── lca.png │ ├── favicon.ico │ ├── fork-1.png │ ├── tournament.png │ ├── NatPro-logo.png │ ├── fenwick-tree.png │ ├── kondenzacija.png │ ├── sparseTable.png │ ├── sparseTable2.png │ ├── sparseTable3.png │ ├── sparseTable4.png │ ├── sparseTable5.png │ ├── binary_search.gif │ ├── fenwick-tree2.png │ ├── implicationGraph.png │ ├── sortiranje_happy_monkey.jpg │ ├── upiti-intervali-matrica-1.png │ ├── upiti-intervali-matrica-2.png │ ├── upiti-intervali-matrica-3.png │ ├── upiti-intervali-matrica-4.png │ ├── upiti-intervali-matrica-5.png │ ├── algoritmi-nad-grafovima-1 │ ├── bf1.png │ ├── bf2.png │ ├── dij1.png │ ├── dij2.png │ ├── fw1.png │ ├── fw2.png │ ├── fw3.png │ ├── mst1.png │ ├── mst2.png │ ├── mst3.png │ ├── mst4.png │ ├── mst5.png │ ├── uf1.png │ ├── uf2.png │ ├── bf_gif.gif │ ├── graph1.png │ ├── adjList1.png │ ├── adjList2.png │ ├── bfs_krivo.png │ ├── parents.png │ ├── pretrazivanje1.png │ ├── pretrazivanje2.png │ ├── pretrazivanje3.png │ ├── pretrazivanje4.png │ ├── terminologija1.png │ ├── terminologija2.png │ ├── terminologija3.png │ └── terminologija4.png │ ├── sortiranje_unimodal_function.png │ ├── XFER-Logo.svg │ ├── codeforces-logo.svg │ └── logo.svg ├── docs ├── upiti-nad-intervalima-1 │ ├── primjeri.md │ ├── fenwickovo-stablo.md │ └── sparse-table.md ├── stringovi │ ├── hash.md │ ├── trie.md │ └── osnovni-pojmovi.md ├── sablona │ ├── sablona-blog.md │ └── sablona-clanak.md ├── doprinos-ovim-materijalima │ ├── prijava-pogreske.md │ ├── autori.md │ ├── kako-napisati-clanak.md │ └── upute-za-markdown.md ├── potpuno-pretrazivanje-i-pohlepni-pristupi │ ├── zadatci-potpuno.md │ ├── zadatci-pohlepni.md │ ├── poslovi.md │ ├── meet-in-the-middle.md │ ├── pohlepni-pristupi.md │ └── pruning.md ├── o-materijalima │ ├── preduvjeti.md │ ├── sadrzaj.md │ └── savjeti.md ├── upiti-nad-intervalima-2 │ └── offline-algoritmi.md ├── algoritmi-nad-grafovima-1 │ ├── mst.md │ ├── union-find-struktura.md │ └── uvod-u-grafove.md ├── algoritmi-nad-grafovima-2 │ ├── 2SAT.md │ ├── kosarajuev-algoritam.md │ └── LCA.md ├── dinamicko-programiranje │ ├── knapsack.md │ ├── najdulji-rastuci-podniz.md │ ├── tiling.md │ ├── problem-razmjene-novca.md │ └── sto-je-dinamicko-programiranje.md ├── sortiranje-i-pretrazivanje │ ├── primjeri.md │ ├── ternarno-pretrazivanje.md │ ├── binarno-pretrazivanje.md │ └── sortiranje.md └── matematika │ ├── kineski-teorem-o-ostatcima.md │ ├── multiplikativni-inverz.md │ ├── prosti-brojevi.md │ ├── osnove-geometrije.md │ ├── vazne-formule.md │ ├── najblizi-par-tocaka.md │ └── convex-hull.md ├── babel.config.js ├── README.md ├── src ├── react_components │ ├── author.js │ └── spoiler.js ├── pages │ ├── problems.js │ ├── styles.module.css │ └── index.js └── css │ └── custom.css ├── blog └── 2021-03-02-dobrodosli.md ├── .github ├── workflows │ └── main.yml └── ISSUE_TEMPLATE │ └── prijava-pogre-ke.md ├── package.json ├── i18n └── en │ └── o-materijalima │ ├── preduvjeti.md │ ├── sadrzaj.md │ └── savjeti.md ├── .gitignore ├── sidebars.js └── docusaurus.config.js /static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/CNAME: -------------------------------------------------------------------------------- 1 | materijali.xfer.hr -------------------------------------------------------------------------------- /static/img/Scc-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/Scc-1.png -------------------------------------------------------------------------------- /static/img/dag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/dag1.png -------------------------------------------------------------------------------- /static/img/lca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/lca.png -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/fork-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/fork-1.png -------------------------------------------------------------------------------- /docs/upiti-nad-intervalima-1/primjeri.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Primjeri 3 | --- 4 | 5 | primjeri 6 | -------------------------------------------------------------------------------- /static/img/tournament.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/tournament.png -------------------------------------------------------------------------------- /static/img/NatPro-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/NatPro-logo.png -------------------------------------------------------------------------------- /static/img/fenwick-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/fenwick-tree.png -------------------------------------------------------------------------------- /static/img/kondenzacija.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/kondenzacija.png -------------------------------------------------------------------------------- /static/img/sparseTable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sparseTable.png -------------------------------------------------------------------------------- /static/img/sparseTable2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sparseTable2.png -------------------------------------------------------------------------------- /static/img/sparseTable3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sparseTable3.png -------------------------------------------------------------------------------- /static/img/sparseTable4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sparseTable4.png -------------------------------------------------------------------------------- /static/img/sparseTable5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sparseTable5.png -------------------------------------------------------------------------------- /static/img/binary_search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/binary_search.gif -------------------------------------------------------------------------------- /static/img/fenwick-tree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/fenwick-tree2.png -------------------------------------------------------------------------------- /static/img/implicationGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/implicationGraph.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /static/img/sortiranje_happy_monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sortiranje_happy_monkey.jpg -------------------------------------------------------------------------------- /static/img/upiti-intervali-matrica-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/upiti-intervali-matrica-1.png -------------------------------------------------------------------------------- /static/img/upiti-intervali-matrica-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/upiti-intervali-matrica-2.png -------------------------------------------------------------------------------- /static/img/upiti-intervali-matrica-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/upiti-intervali-matrica-3.png -------------------------------------------------------------------------------- /static/img/upiti-intervali-matrica-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/upiti-intervali-matrica-4.png -------------------------------------------------------------------------------- /static/img/upiti-intervali-matrica-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/upiti-intervali-matrica-5.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/bf1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/bf1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/bf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/bf2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/dij1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/dij1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/dij2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/dij2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/fw1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/fw1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/fw2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/fw2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/fw3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/fw3.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/mst1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/mst1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/mst2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/mst2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/mst3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/mst3.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/mst4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/mst4.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/mst5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/mst5.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/uf1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/uf1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/uf2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/uf2.png -------------------------------------------------------------------------------- /static/img/sortiranje_unimodal_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/sortiranje_unimodal_function.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/bf_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/bf_gif.gif -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/graph1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/graph1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/adjList1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/adjList1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/adjList2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/adjList2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/bfs_krivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/bfs_krivo.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/parents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/parents.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/pretrazivanje1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/pretrazivanje1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/pretrazivanje2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/pretrazivanje2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/pretrazivanje3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/pretrazivanje3.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/pretrazivanje4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/pretrazivanje4.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/terminologija1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/terminologija1.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/terminologija2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/terminologija2.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/terminologija3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/terminologija3.png -------------------------------------------------------------------------------- /static/img/algoritmi-nad-grafovima-1/terminologija4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x-fer/natpro/HEAD/static/img/algoritmi-nad-grafovima-1/terminologija4.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Materijali za natjecateljsko programiranje 2 | 3 | Materijali su napravljeni koristeći [Docusaurus 2](https://v2.docusaurus.io/). 4 | 5 | Upute za doprinos su dostupne na [stranici](https://materijali.xfer.hr/docs/doprinos-ovim-materijalima/kako-napisati-clanak). 6 | -------------------------------------------------------------------------------- /docs/stringovi/hash.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hash 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Članak je u izradi. 12 | -------------------------------------------------------------------------------- /docs/stringovi/trie.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Trie 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Članak je u izradi. 12 | -------------------------------------------------------------------------------- /docs/sablona/sablona-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Naslov velikim slovom 3 | author: Ime Prezime 4 | author_title: Titula @ Ustanova 5 | author_url: GitHub ili tako neki URL 6 | author_image_url: URL neke lijepe slike 7 | tags: [oznake, odvojene, zarezima] 8 | --- 9 | 10 | Ovdje ide sadržaj blog objave 11 | -------------------------------------------------------------------------------- /docs/stringovi/osnovni-pojmovi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Osnovni pojmovi 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Članak je u izradi. 12 | -------------------------------------------------------------------------------- /docs/sablona/sablona-clanak.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Naslov velikim slovom 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Ovdje ide sadržaj članka 12 | -------------------------------------------------------------------------------- /src/react_components/author.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Author = ({authorName, githubUsername}) => { 4 | return ( 5 |
6 | Autor: {authorName} 7 |
8 | ) 9 | } 10 | 11 | export default Author 12 | -------------------------------------------------------------------------------- /src/react_components/spoiler.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const {useState} = React; 3 | 4 | const Spoiler = ({text}) => { 5 | const [shown, setShown] = useState(false) 6 | 7 | return ( 8 | setShown(!shown)} className={'react-spoiler-' + (shown ? 'shown' : 'hidden')}> 9 | {text} 10 | 11 | ) 12 | } 13 | 14 | export default Spoiler 15 | -------------------------------------------------------------------------------- /docs/doprinos-ovim-materijalima/prijava-pogreske.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prijava pogreške 3 | --- 4 | 5 | U slučaju da ste u materijalima pronašli neku grešku, molimo vas da ju prijavite tako da prijavite [_issue_](https://github.com/x-fer/natpro/issues) na GitHubu. Sve što trebate napraviti je pritisnuti na gumb _New issue_, opisati o kakvoj se pogrešci radi, dodati vezu na stranicu na kojoj ste našli pogrešku, i ako možete, predložiti rješenje. 6 | -------------------------------------------------------------------------------- /src/pages/problems.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from '@theme/Layout'; 3 | 4 | function Problems() { 5 | return ( 6 | 7 |
15 |

16 | Edit pages/problems.js and save to reload. 17 |

18 |
19 |
20 | ); 21 | } 22 | 23 | export default Problems; -------------------------------------------------------------------------------- /blog/2021-03-02-dobrodosli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dobrodošli 3 | author: Ivan Vlahov 4 | author_title: Student @ FER, Autor @ NatPro 5 | author_url: https://github.com/vlahovivan 6 | author_image_url: https://avatars.githubusercontent.com/u/30372377?s=460&u=f7d79f7021f03f3a299ca6349c77dd134fb6283b&v=4 7 | tags: [dobrodosli, natpro, bitno] 8 | --- 9 | 10 | Dobrodošli na stranicu NatPro! Da biste započeli s radom, odite na stranicu Materijali gdje možete pronaći mnoštvo članaka o natjecateljskom programiranju te naučiti puno toga, a ako ste već upoznati s konceptima natjecateljskog programiranja, možete ponoviti gradivo! 11 | 12 | Sretno s učenjem! 13 | -------------------------------------------------------------------------------- /docs/doprinos-ovim-materijalima/autori.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Autori 3 | --- 4 | 5 | Autori članaka na ovoj stranici su (abecednim redom): 6 | - [Adrian Brajković](https://github.com/Brajk19) 7 | - [Karlo Franić](https://github.com/kfranic1) 8 | - [Martin Josip Kocijan](https://github.com/kocijan) 9 | - [Anamarija Kozina](https://github.com/AnamarijaKozina) 10 | - [Petar Mihalj](https://github.com/PetarMihalj) 11 | - [Maja Milas](https://github.com/javascript-m) 12 | - [Ivan Vlahov](https://github.com/vlahovivan) 13 | 14 | 15 | U slučaju da ste napisali članak, a niste dodani na ovaj popis, molimo da se obratite na [našu e-mail adresu](mailto:ivan.vlahov@gmail.com). 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup Node 11 | uses: actions/setup-node@v2 12 | with: 13 | node-version: "18.20.2" 14 | 15 | - name: Checkout 🛎️ 16 | uses: actions/checkout@v2.3.1 17 | 18 | - name: Install and Build 🔧 19 | run: | 20 | npm install 21 | npm run build 22 | 23 | - name: Deploy 🚀 24 | uses: JamesIves/github-pages-deploy-action@4.1.4 25 | with: 26 | branch: gh-pages # The branch the action should deploy to. 27 | folder: build # The folder the action should deploy. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/prijava-pogre-ke.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Prijava pogreške 3 | about: Prijavite pogrešku kako bismo povećali kvalitetu materijala 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Ako se radi o pogrešci u materijalima** 11 | Objasnite u čemu je problem, na kojoj stranici se problem nalazi, te ako je moguće, predložite rješenje problema. 12 | 13 | **Ako se radi o tehničkoj pogrešci na stranici** 14 | Objasnite u čemu je problem, na kojoj stranici se nalazi, kako ga rekreirati, te upišite podatke o uređaju na kojem ste naišli na problem, kao i ime i verziju preglednika kojeg ste koristili. Ako je primjenjivo, priložite i snimku zaslona. 15 | 16 | **Dodatne informacije** 17 | Upišite bilo kakve dodatne informacije za koje mislite da bi nam mogle biti korisne pri rješavanju problema. 18 | -------------------------------------------------------------------------------- /src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | background: linear-gradient(90deg, #FF5F6D, #FFC371); 10 | padding: 4rem 0; 11 | text-align: center; 12 | position: relative; 13 | overflow: hidden; 14 | } 15 | 16 | @media screen and (max-width: 966px) { 17 | .heroBanner { 18 | padding: 2rem; 19 | } 20 | } 21 | 22 | .buttons { 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .features { 29 | display: flex; 30 | align-items: center; 31 | padding: 2rem 0; 32 | width: 100%; 33 | } 34 | 35 | .featureImage { 36 | height: 200px; 37 | width: 200px; 38 | } 39 | 40 | .latex { 41 | height: max-content; 42 | width: max-content; 43 | } -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/zadatci-potpuno.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Zadatci 1" 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | ### Zadatak: Permutacije 10 | 11 | Zadan je broj $n$. Ispišite sve permutacije niza $1, 2, ..., n$. 12 | 13 | 1. Koristeći rekurzivni pristup 14 | 2. Koristeći ugrađenu funkciju [next_permutation](https://www.cplusplus.com/reference/algorithm/next_permutation/) 15 | 16 | Rekurzivni pristup koristi tehniku koju smo opisali u poglavlju *pruning*: 17 | 18 | - postoji vektor kojeg dijele rekurzivni pozivi (šalje se preko reference) 19 | - rekurzivni pozivi na mjesto $i$ stavljaju svaki od elemenata na mjestima $i$ do $n-1$, granaju se, a zatim poništavaju promjenu 20 | 21 | ### Ostali zadatci 22 | 23 | Tutoriali za sve zadatke dostupni u *Contest materials* s desne strane. 24 | 25 | - [Points on the line](https://codeforces.com/problemset/problem/940/A) 26 | - [Vitamins](https://codeforces.com/problemset/problem/1042/B) 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "serve": "docusaurus serve", 12 | "clear": "docusaurus clear", 13 | "write-translations": "docusaurus write-translations" 14 | }, 15 | "dependencies": { 16 | "@docusaurus/core": "3.0.0", 17 | "@docusaurus/preset-classic": "3.0.0", 18 | "@mdx-js/react": "^3.0.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "clsx": "^1.1.1", 22 | "rehype-katex": "^7.0.0", 23 | "remark-math": "^6.0.0" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.5%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | }, 37 | "engines": { 38 | "node": "18.20.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/o-materijalima/preduvjeti.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Preduvjeti 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | :::tipsavjet 12 | 13 | Ako već imate osnovno znanje programskog jezika C++ i imate instaliran neki uređivač teksta, možete preskočiti ovaj članak. 14 | 15 | ::: 16 | 17 | ### C++ 18 | 19 | U člancima ovih materijala pretpostavljamo da imate barem osnovno znanje programskog jezika C++. U slučaju da ste apsolutni početnik, preporučamo da pogledate neki _crash course_ na _YouTubeu_. 20 | 21 | ### Uređivač teksta 22 | 23 | Ako niste izabrali uređivač teksta u kojem ćete programirati, preporučujemo [Visual Studio Code](https://code.visualstudio.com/). Neki dijelovi ovih materijala će govoriti o ubrzavanju brzine pisanja koda te će neke metode ubrzavanja biti specifične za VSC. Ova stavka nije obvezna, ali je svakako preporučena ako već nemate preferirani uređivač teksta. 24 | 25 | ### Preporučene postavke 26 | 27 | Na sljedećim YouTube videima možete pronaći kako postaviti okruženje koje ćemo mi koristiti: 28 | - [MinGW i g++](https://www.youtube.com/watch?v=guM4XS43m4I) 29 | - [Instalacija VSC-a](https://www.youtube.com/watch?v=JGsyJI8XG0Y) 30 | - [Postavljanje VSC-a za programiranje u C++-u](https://www.youtube.com/watch?v=77v-Poud_io) 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/zadatci-pohlepni.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Zadatci 2" 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | ### Zadatak: More Cowbell 10 | 11 | Tekst zadatka: [More Cowbell](https://codeforces.com/problemset/problem/604/B) 12 | 13 |
14 | 15 | Rješenje 16 | 17 |
18 | Zamislite da imamo 10 zvona i 7 kutija. Ako stavljamo po jedno zvono u svaku kutiju počeviši od najvećeg, preostat će nam tri najmanja zvona koja ćemo morati raspodijeliti u kutije s prethodnima. Da bismo postigli najmanju moguću veličinu kutije, najveće od preostalih zvona stavit ćemo s najmanjim od zvona u kutijama i tako redom. Pri svakom stavljanju zvona u kutiju mjerimo veličinu kutije i pamtimo najveću izmjerenu. Složenost ovog algoritma je O(n), a spada u pohlepne algoritme. Više o pohlepnim algoritmima pročitajte ovdje. 19 |
20 |
21 | 22 | ### Ostali zadaci 23 | 24 | - [Turn the rectangles](https://codeforces.com/problemset/problem/1008/B) 25 | - [Gambling](https://codeforces.com/problemset/problem/1038/C) 26 | - [String transformation](https://codeforces.com/problemset/problem/946/C) 27 | - [Minimum ternary string](https://codeforces.com/problemset/problem/1009/B) 28 | -------------------------------------------------------------------------------- /i18n/en/o-materijalima/preduvjeti.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Preduvjeti 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | :::tipsavjet 12 | 13 | If you already have a basic knowledge of the C ++ programming language and have a text editor installed, you can skip this article. 14 | 15 | ::: 16 | 17 | ### C++ 18 | 19 | In the articles in these materials, we assume that you have at least a basic knowledge of the C ++ programming language. In case you are an absolute beginner, we recommend that you watch some _crash course_ on _YouTube_. 20 | 21 | ### Text editor 22 | 23 | If you have not selected a text editor in which to program, we recommend [Visual Studio Code](https://code.visualstudio.com/). Some parts of these materials will talk about speeding up code writing speed, and some speeding methods will be specific to VSC. This item is optional, but is definitely recommended if you don't already have a preferred text editor. 24 | 25 | ### Recommended settings 26 | 27 | In the following YouTube videos you can find how to set up an environment that we will use: 28 | - [MinGW & g++](https://www.youtube.com/watch?v=guM4XS43m4I) 29 | - [VSC installation](https://www.youtube.com/watch?v=JGsyJI8XG0Y) 30 | - [Setting up a VSC for programming in C++](https://www.youtube.com/watch?v=77v-Poud_io) 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /static/img/XFER-Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/o-materijalima/sadrzaj.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sadržaj 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Za koga su ovi materijali? 12 | 13 | Ovi materijali su namijenjeni svima koji žele unaprijediti svoje znanje o programiranju, algoritmima i strukturama podataka, onima koji se žele natjecati na natjecanjima iz programiranja, onima koji žele naučiti neke "trikove" za rješavanje nekih problema, onima koji se žele pripremiti za razgovore za posao te svima drugima kojima se tema natjecateljskog programiranja čini zanimljivom. Također su namijenjeni i onima koji su upoznati s principima natjecateljskog programiranja, a žele ponoviti gradivo. 14 | 15 | ### Tko je napravio ove materijale? 16 | 17 | Ivan Vlahov, student na Fakultetu Elektrotehnike i Računarstva, napravio je stranicu, dok su za sadržaj na stranici odgovorni odlični [autori članaka](../doprinos-ovim-materijalima/autori). 18 | 19 | ### Što očekivati od ovih materijala? 20 | 21 | Možete očekivati sadržaj koji obuhvaća gradivo natjecanja iz informatike za osnovne i srednje škole, gradivo prve i druge godine računarstva te neke matematičke koncepte koje ćemo se potruditi objasniti na dovoljno jednostavan način da budu jasni i onima koji se s tim konceptima susreću prvi put. Također možete očekivati i mnoštvo primjera s pravih natjecanja uz rješenja. 22 | 23 | ### Što ne očekivati od ovih materijala? 24 | 25 | Nemojte očekivati da ćete **samo** čitanjem ovih materijala postati vrsni natjecatelj. Ovi materijali objašnjavaju koncepte natjecateljskog programiranja koje možete u potpunosti naučiti jedino rješavanjem zadataka koje uključuju te koncepte. Samo učenjem algoritama napamet nećete puno postići, zato savjetujemo da isprobate riješiti zadatke koje ćete moći pronaći na krajevima nekih članaka. 26 | 27 | 28 | -------------------------------------------------------------------------------- /i18n/en/o-materijalima/sadrzaj.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sadržaj 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Who are these materials for? 12 | 13 | These materials are intended for anyone who wants to improve their knowledge of programming, algorithms, and data structures, those who wish to compete in programming competitions, those who want to learn some "tricks" to solve some problems, those who want to prepare for job interviews and to all others who find the topic of competitive programming interesting. They are also intended for those familiar with the principles of competition programming and want to repeat the material. 14 | 15 | ### Who created these materials? 16 | 17 | Ivan Vlahov, a student at the Faculty of Electrical Engineering and Computing, created the page, while the excellent [autori članaka](../doprinos-ovim-materijalima/autori) are responsible for the content on the page. 18 | 19 | ### What to expect from these materials? 20 | 21 | You can expect content that includes the material of computer science competitions for primary and secondary schools, the material of the first and second year of computer science degrees and some mathematical concepts that we will try to explain in a simple enough way to be clear to those who encounter these concepts. You can also expect a lot of examples from real competitions with solutions. 22 | 23 | ### What not to expect from these materials? 24 | 25 | Don’t expect that **just** by readin these materials you will become a great competitor. These materials explain the concepts of competition programming that you can fully learn only by solving tasks that involve those concepts. Just learning the algorithms by heart will not accomplish much, so we advise you to try to solve the tasks located at the ends of the articles. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | package-lock.json 10 | yarn.lock 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Microbundle cache 60 | .rpt2_cache/ 61 | .rts2_cache_cjs/ 62 | .rts2_cache_es/ 63 | .rts2_cache_umd/ 64 | 65 | # Optional REPL history 66 | .node_repl_history 67 | 68 | # Output of 'npm pack' 69 | *.tgz 70 | 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | # dotenv environment variables file 75 | .env 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # TernJS port file 107 | .tern-port 108 | 109 | # User added stuff 110 | .vscode 111 | **/test.* 112 | build/* 113 | 114 | # Docusaurus gitignore 115 | 116 | # Dependencies 117 | /node_modules 118 | 119 | # Generated files 120 | .docusaurus 121 | .cache-loader 122 | 123 | # Misc 124 | .DS_Store 125 | .env.local 126 | .env.development.local 127 | .env.test.local 128 | .env.production.local 129 | -------------------------------------------------------------------------------- /src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #ff5f6d; 11 | --ifm-color-primary-dark: #ff3c4d; 12 | --ifm-color-primary-darker: #ff2a3d; 13 | --ifm-color-primary-darkest: #f50015; 14 | --ifm-color-primary-light: #ff828d; 15 | --ifm-color-primary-lighter: #ff949d; 16 | --ifm-color-primary-lightest: #ffc8cd; 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .navbar__link--active, .navbar__link:hover { 21 | /* background: linear-gradient(90deg,#FF5F6D, #FFC371); */ 22 | color: var(--ifm-color-primary); 23 | /* background: var(--ifm-color-primary); 24 | background-clip: text; 25 | -webkit-background-clip: text; 26 | -webkit-text-fill-color: transparent; 27 | -apple-background-clip: text; 28 | -apple-text-fill-color: transparent; */ 29 | } 30 | 31 | .menu__list-item-collapsible:hover, .menu__link:hover, .menu__link--active, .footer__link-item:hover { 32 | /* background: linear-gradient(90deg,#FF5F6D, #FFC371); */ 33 | background: #FF5F6D; 34 | color: var(--ifm-menu-color); 35 | /* background-clip: text; 36 | -webkit-background-clip: text; 37 | -webkit-text-fill-color: transparent; 38 | -apple-background-clip: text; 39 | -apple-text-fill-color: transparent; */ 40 | } 41 | 42 | .menu__link--active:not(.menu__link--sublist) { 43 | /* background: linear-gradient(90deg,#FF5F6D, #FFC371); */ 44 | background: #FF5F6D; 45 | /* background-color: #FFC371; */ 46 | -webkit-text-fill-color: var(--ifm-menu-color); 47 | -apple-text-fill-color: var(--ifm-menu-color); 48 | } 49 | 50 | .docusaurus-highlight-code-line { 51 | background-color: rgb(72, 77, 91); 52 | display: block; 53 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 54 | padding: 0 var(--ifm-pre-padding); 55 | } 56 | 57 | /*Images*/ 58 | img[alt=potter] { width: 300px; } 59 | img[alt=monkey] { height: 200px } 60 | 61 | /*Spoiler*/ 62 | .react-spoiler-shown { 63 | cursor: pointer; 64 | background: rgba(124, 124, 124, 0.4); 65 | } 66 | 67 | .react-spoiler-hidden { 68 | background: rgb(124, 124, 124); 69 | color: rgb(124, 124, 124); 70 | cursor: pointer; 71 | } 72 | 73 | .author { 74 | font-weight: 700; 75 | } 76 | 77 | .author + :not(h1, h2, h3, h4, h5, h6) { 78 | margin-top: 3%; 79 | } 80 | 81 | article img { 82 | background: white; 83 | } 84 | -------------------------------------------------------------------------------- /sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | materijaliSidebar: { 3 | "0. O materijalima": ["sadrzaj", "preduvjeti", "savjeti"].map( 4 | (title) => "o-materijalima/" + title 5 | ), 6 | "1. Osnove natjecateljskog programiranja": [ 7 | "osnovni-pojmovi", 8 | "o-dobrim-algoritmima", 9 | "bitni-containeri", 10 | ].map((title) => "osnove-natjecateljskog-programiranja/" + title), 11 | "2. Sortiranje i pretraživanje": [ 12 | "sortiranje", 13 | "binarno-pretrazivanje", 14 | "ternarno-pretrazivanje", 15 | "primjeri", 16 | ].map((title) => "sortiranje-i-pretrazivanje/" + title), 17 | "3. Potpuno pretraživanje i pohlepni pristupi": [ 18 | "uvod", 19 | "meet-in-the-middle", 20 | "pruning", 21 | "zadatci-potpuno", 22 | "pohlepni-pristupi", 23 | "poslovi", 24 | "zadatci-pohlepni", 25 | ].map((title) => "potpuno-pretrazivanje-i-pohlepni-pristupi/" + title), 26 | "4. Dinamičko programiranje": [ 27 | "sto-je-dinamicko-programiranje", 28 | "problem-razmjene-novca", 29 | "najdulji-rastuci-podniz", 30 | "knapsack", 31 | "tiling", 32 | ].map((title) => "dinamicko-programiranje/" + title), 33 | "5. Upiti nad intervalima 1": [ 34 | "upiti-nad-statickim-poljima", 35 | "difference-array", 36 | "sparse-table", 37 | "fenwickovo-stablo", 38 | ].map((title) => "upiti-nad-intervalima-1/" + title), 39 | "6. Upiti nad intervalima 2": ["segmentno-stablo", "offline-algoritmi"].map( 40 | (title) => "upiti-nad-intervalima-2/" + title 41 | ), 42 | "7. Algoritmi nad grafovima 1": [ 43 | "uvod-u-grafove", 44 | "zapisi-grafova", 45 | "pretrazivanje-grafova", 46 | "najkraci-putovi", 47 | "union-find-struktura", 48 | "mst", 49 | ].map((title) => "algoritmi-nad-grafovima-1/" + title), 50 | "8. Algoritmi nad grafovima 2": [ 51 | "topolosko-sortiranje", 52 | "kosarajuev-algoritam", 53 | "2SAT", 54 | "LCA", 55 | ].map((title) => "algoritmi-nad-grafovima-2/" + title), 56 | "9. Matematika": [ 57 | "prosti-brojevi", 58 | "vazne-formule", 59 | "kineski-teorem-o-ostatcima", 60 | "multiplikativni-inverz", 61 | "osnove-geometrije", 62 | "convex-hull", 63 | "najblizi-par-tocaka", 64 | ].map((title) => "matematika/" + title), 65 | "10. Stringovi": ["osnovni-pojmovi", "hash", "trie"].map( 66 | (title) => "stringovi/" + title 67 | ), 68 | }, 69 | doprinosSidebar: { 70 | "Doprinos ovim materijalima": [ 71 | "kako-napisati-clanak", 72 | "prijava-pogreske", 73 | "upute-za-markdown", 74 | "autori", 75 | ].map((title) => "doprinos-ovim-materijalima/" + title), 76 | }, 77 | }; 78 | -------------------------------------------------------------------------------- /i18n/en/o-materijalima/savjeti.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Savjeti 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Within these materials, there will be examples of tasks you can try to solve to check how much you have learned. All examples will be from the [Codeforces](https://codeforces.com). For this reason, we advise you to open a free account on this page so that you can track your own progress, and in time you can start participating in the competitions they organize. 12 | 13 | Competitions on the Codeforces platform are divided into three categories. Div. 3 is a category for absolute beginners (so-called newbies) and competitions are rarely held in this category. Div. 2 is a category for those a little more advanced and competitions in this category are held relatively often. Div. 1 is a category for advanced developers and competitions in this category are rarely held. 14 | 15 | Our advice is to try the _Virtual participation_ option of a contest (e.g. [this one](https://codeforces.com/contest/1462)) before you actually enter for the first time. That way you can see who you would be if you actually competed in that competition. 16 | 17 | After each competition, _editorial_ is published, ie the solution of the tasks with explanations. In case you are not familiar with some of the concepts you find in these solutions, we recommend that you _google_ them and try to solve this task with your new knowledge. 18 | 19 | If you want to track your own statistics on Codeforces, you can use [this tool](https://recommender.codedrills.io/) which, based on the labels of tasks you have solved and those you have not been able to solve, assesses which categories are good and which are good. You still need to work and suggest exercise tasks based on this information. 20 | 21 | We also advise you to join our [Discord server](https://discord.gg/E7ad4UGbrG) where you can find interesting people who are also involved in competition programming and who are ready to help you in case you get stuck somewhere. 22 | 23 | In addition, as ridiculous as it may sound, it is important to know how to _google_ well. Whenever something is not clear to you or you get stuck somewhere, it is important to know how to ask a good question. [This YouTube video](https://www.youtube.com/watch?v=cEBkvm0-rg0) explains some of the more advanced _google_ methods very well. These materials talk about the general concepts needed to solve many problems, however, sometimes you get stuck in the implementation of a specific form of an algorithm and need help with it. For such situations, I recommend searching the [GeeksForGeeks](https://www.geeksforgeeks.org/) page where you can find specific implementations of certain algorithms. 24 | 25 | Good luck with your learning! -------------------------------------------------------------------------------- /docs/o-materijalima/savjeti.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Savjeti 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Unutar ovih materijala nalazit će se primjeri zadataka koje možete pokušati riješiti te tako provjeriti koliko ste naučili. Većina primjera bit će preuzeta sa stranice [Codeforces](https://codeforces.com). Iz tog razloga savjetujemo vam da na toj stranici otvorite besplatni račun da biste mogli pratiti vlastiti napredak, te možete početi sudjelovati i na natjecanjima koje oni organiziraju. 12 | 13 | Natjecanja na platformi Codeforces su podijeljena u tri kategorije. Div. 3 je kategorija za apsolutne početnike (tzv. *newbies*) te se u toj kategoriji natjecanja rijetko održavaju. Div. 2 je kategorija za one malo naprednije te se natjecanja u toj kategoriji održavaju relativno često. Div. 1 je kategorija za napredne programere te se natjecanja u toj kategoriji održavaju rijetko. 14 | 15 | Naš savjet je da isprobate opciju _Virtual participation_ nekog natjecanja (npr. [ovog](https://codeforces.com/contest/1462)) prije nego se prvi put zapravo prijavite. Na taj način možete vidjeti koji biste bili da ste se zapravo natjecali na tom natjecanju. 16 | 17 | Nakon svakog natjecanja se objavi _editorial_, tj. rješenje zadataka s objašnjenjima. U slučaju da niste upoznati s nekim pojmom koji pronađete u tim rješenjima, preporučujemo da ih _proguglate_ te pokušate riješiti taj zadatak sa svojim novim saznanjima. 18 | 19 | Ako želite pratiti vlastitu statistiku na Codeforcesu, možete koristiti [ovaj alat](https://recommender.codedrills.io/) koji na temelju oznaka zadataka koje ste riješili i onih koje niste uspjeli riješiti procjenjuje u kojim se kategorijama dobri, a na kojima trebate još raditi te vam na temelju tih podataka predlaže zadatke za vježbu. 20 | 21 | Također savjetujemo da se pridružite i našem [Discord serveru](https://discord.gg/E7ad4UGbrG) na kojem možete pronaći zanimljive ljude koji se također bave natjecateljskim programiranjem te koji su vam spremni pomoći u slučaju da negdje zapnete. 22 | 23 | Osim toga, koliko god možda zvučalo smiješno, bitno je znati dobro _guglati_. Kad god vam nešto nije jasno ili zapnete negdje, bitno je znati postaviti dobar upit. [Ovaj YouTube video](https://www.youtube.com/watch?v=cEBkvm0-rg0) jako dobro objašnjava neke naprednije metode _guglanja_. Ovi materijali govore o općenitim konceptima koji su potrebni za rješavanje mnoštva problema, međutim, nekad se zna dogoditi da zapnete u implementaciji nekog specifičnog oblika nekog algoritma te trebate pomoć s njim. Za takve situacije preporučujem da pretražujete stranicu [GeeksForGeeks](https://www.geeksforgeeks.org/) na kojoj možete pronaći specifične implementacije određenih algoritama. 24 | 25 | Sretno s učenjem! 26 | 27 | 28 | -------------------------------------------------------------------------------- /static/img/codeforces-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 35 | 37 | 39 | 41 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import Layout from "@theme/Layout"; 4 | import Link from "@docusaurus/Link"; 5 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 6 | import useBaseUrl from "@docusaurus/useBaseUrl"; 7 | import styles from "./styles.module.css"; 8 | 9 | const features = [ 10 | { 11 | title: "Za početnike i one naprednije", 12 | imageUrl: "img/index/Asking_questions.svg", 13 | description: ( 14 | <> 15 | Ovi materijali su koncipirani tako da budu dovoljno jednostavni za 16 | početnike, a dovoljno temeljiti i za one naprednije. Upoznat ćete se s 17 | brojnim algoritmima i strukturama podataka te nekim trikovima za brže 18 | rješavanje zadataka. 19 | 20 | ), 21 | }, 22 | { 23 | title: "Puno primjera za kvalitetno učenje", 24 | imageUrl: "img/index/Startup_Idea.svg", 25 | description: ( 26 | <> 27 | Svaka tema popraćena je mnoštvom primjera s održanih natjecanja tako da 28 | možete učiti i pripremati se za svoje natjecateljske avanture! 29 | 30 | ), 31 | }, 32 | { 33 | title: "Možete i doprinijeti", 34 | imageUrl: "img/index/File_Transfer.svg", 35 | description: ( 36 | <> 37 | Ako primijetite da neka tema nedostaje, možete i sami napisati članak o 38 | njoj! Pročitajte upute u poglavlju "Doprinos ovim materijalima" te 39 | pomozite obogatiti sadržaj ove stranice! 40 | 41 | ), 42 | }, 43 | ]; 44 | 45 | function Feature({ imageUrl, title, description }) { 46 | const imgUrl = useBaseUrl(imageUrl); 47 | return ( 48 |
49 | {imgUrl && ( 50 |
51 | {title} 52 |
53 | )} 54 |

{title}

55 |

{description}

56 |
57 | ); 58 | } 59 | 60 | function Home() { 61 | const context = useDocusaurusContext(); 62 | const { siteConfig = {} } = context; 63 | return ( 64 | 68 |
69 |
70 |

{siteConfig.title}

71 |

{siteConfig.tagline}

72 |
73 | 80 | Započnite 81 | 82 |
83 |
84 |
85 |
86 | {features && features.length > 0 && ( 87 |
88 |
89 |
90 | {features.map((props, idx) => ( 91 | 92 | ))} 93 |
94 |
95 |
96 | )} 97 |
98 |
99 | ); 100 | } 101 | 102 | export default Home; 103 | -------------------------------------------------------------------------------- /docs/upiti-nad-intervalima-2/offline-algoritmi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Offline algoritmi 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Uvod 12 | 13 | U nekim je zadacima s upitima potrebno rješavati upite jedan po jedan, onim redom kojim su zadani. Tada gradimo neku strukturu koja odgovara na upite u složenosti npr. $O(1)$ ili $O(\log N)$. Naravno, te složenosti mogu biti i amortizirane ako nam to treba. 14 | 15 | Često ne moramo ispisati odgovor na upit prije nego se unosi novi upit, i prethodni upiti ne utječu na stanje naše strukture za novi upit. Možemo reći da su svi upiti međusobno nezavisni i nebitan je redoslijed kojim na njih odgovaramo. Zato možemo poredati upite onako kako nama najviše odgovara, preprocesuirati ih sve odjednom, i ispisati odgovore u izvornom poretku. 16 | 17 | ### Primjer 18 | 19 | Riješimo sljedeći zadatak s Mo's algorithm / sqrt decomposition. 20 | 21 | - [Powerful array](https://codeforces.com/contest/86/problem/D) 22 | 23 | Trik je da izaberemo neki $K$ i podijelimo niz duljine $N$ na $K$ bucketa duljine $\left\lfloor{\frac{N}{K}}\right\rfloor$ (zadnji skratimo po potrebi). Zatim ćemo prvo riješiti sve upite kojima je lijevi indeks unutar $1.$ bucketa, pa sve upite kojima je lijeva granica unutar $2.$ bucketa, i tako sve do $K$-tog bucketa. Unutar nekog bucketa pak rješavamo upite tako da sortiramo desnu granicu, a lijevu pomičemo po potrebi unutar bucketa. Naša će složenost biti $O(N \cdot \frac{N}{K} + N \cdot K)$, a može se dokazati da to poprima minimum za $K = \sqrt{N}$, te je složenost našeg rješenja $O(N \sqrt{N})$. 24 | 25 | ```cpp 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | using namespace std; 32 | typedef long long ll; 33 | const int MaxN = 200005; 34 | const int MaxA = 1000005; 35 | const int BLOC = 450; 36 | 37 | struct query{ 38 | int L, R; 39 | int j; //indeks 40 | ll res; 41 | }; 42 | 43 | bool cmp(query X, query Y){ 44 | if (X.L / BLOC != Y.L / BLOC) 45 | return X.L / BLOC < Y.L / BLOC; 46 | 47 | return X.R < Y.R; 48 | } 49 | 50 | bool cmp_end(query X, query Y){ 51 | return X.j < Y.j; 52 | } 53 | 54 | query Q[MaxN]; 55 | int a[MaxN]; 56 | int c[MaxA]; 57 | int N, M; 58 | 59 | ll add(int pos){ 60 | c[a[pos]]++; 61 | return (ll)a[pos] * (2*c[a[pos]] - 1); 62 | } 63 | 64 | ll rem(int pos){ 65 | c[a[pos]]--; 66 | return (ll)a[pos] * (2*c[a[pos]] + 1); 67 | } 68 | 69 | int main(){ 70 | scanf("%d%d", &N, &M); 71 | for (int i = 1; i <= N; i++){ 72 | scanf("%d", a+i); 73 | } 74 | int tL, tR; 75 | for (int i = 0; i < M; i++){ 76 | scanf("%d%d", &tL, &tR); 77 | Q[i].L = tL; Q[i].R = tR; Q[i].j = i; 78 | } 79 | 80 | sort(Q, Q + M, cmp); 81 | int cL = 0, cR = 0; 82 | ll ans = 0; 83 | for (int i = 0; i < M; i++){ 84 | int L = Q[i].L; 85 | int R = Q[i].R; 86 | 87 | while (cL < L){ 88 | ans -= rem(cL); 89 | cL++; 90 | } 91 | while (cL > L){ 92 | cL--; 93 | ans += add(cL); 94 | } 95 | while (cR < R){ 96 | cR++; 97 | ans += add(cR); 98 | } 99 | while (cR > R){ 100 | ans -= rem(cR); 101 | cR--; 102 | } 103 | 104 | Q[i].res = ans; 105 | } 106 | 107 | sort(Q, Q + M, cmp_end); 108 | for (int i = 0; i < M; i++) 109 | cout< 10 | 11 | ## Minimalno razapinjuće stablo (MST) 12 | 13 | **Razapinjuće stablo** nekog grafa je podgraf koji se sastoji od svih njegovih vrhova i nekih bridova tako da **postoji put između svaka dva vrha**. Kao i obična stabla, razapinjuća stabla su povezana i nemaju cikluse. MST je razapinjuće stablo čiji je zbroj težina bridova minimalan. 14 | 15 | mst1 16 | 17 | Na slici lijevo prikazan je neki graf, a desno njegov MST. Sličnom logikom možemo konstruirati i maksimalno razapinjuće stablo (ima maksimalnu sumu težina bridova). 18 | 19 | :::noteprimijetite 20 | 21 | Minimalno (maksimalno) razapinjuće stablo ne mora biti jedinstven graf. 22 | 23 | ::: 24 | 25 | ## Kruskalov algoritam 26 | 27 | Kruskalov je algoritam jedan u nizu algoritama koji **konstruiraju MST**. Na početku ćemo u graf dodati samo vrhove, a potom ćemo dodavati bridove po redu _od manjih prema većima_. Algoritam dodaje brid u stablo ako dodavanjem tog brida ne nastaje ciklus. Kako bi to jednostavno provjeravali, koristit ćemo Union find strukturu. 28 | 29 | Pogledajmo kako bi Kruskalov algoritam radio za graf s prethodne slike. Na početku u stablu imamo samo vrhove i svaki vrh čini zaseban skup. Bridove smo sortirali po težinama. 30 | 31 | mst2 32 | 33 | Prvo dodajemo brid 5-6 te skupove {5} i {6} spajamo funkcijom unite (lijevo). Nakon toga dodajemo bridove 1-2, 3-6 i 1-5 na sličan način spajamo odgovarajuće skupove (desno). 34 | 35 | mst3 36 | 37 | Sada u strukturi imamo 2 skupa: {1,2,3,5,6} i {4}. Sljedeći brid na popisu je 2-3, ali njega nećemo dodavati jer su 2 i 3 unutar iste komponente (dodavanjem bi nastao ciklus). Slično je i za brid 4-5. Na kraju dodamo brid 4-6 i gotovi smo (jej!). 38 | 39 | mst4 40 | 41 | :::noteprimijetite 42 | 43 | Kruskalov algoritam spada u pohlepne algoritme. 44 | 45 | ::: 46 | 47 | ### Zašto ovo radi? 48 | 49 | Ovaj će algoritam uvijek u MST dodati najmanji brid, ali zamislimo da postoji situacija kada to nije rješenje. U našem bi primjeru to značilo da postoji bolje razapinjuće stablo koje ne sadržava brid 5-6. Međutim, ovo ne može biti rješenje zato što uvijek možemo maknuti jedan brid iz takvog grafa i zamijeniti ga s bridom 5-6 što sigurno smanjuje rješenje (primjer na slici ispod). Sličnim razmišljanjem možemo obrazložiti i dodavanje drugih bridova. Dakle, Kruskalov algoritam radi. 50 | 51 | mst5 52 | 53 | ### Implementacija 54 | 55 | Za implementaciju koristimo Union find strukturu i njezine funkcije iz prošle lekcije. Bridove ćemo držati u listi `vector> edgeList` u kojoj je svaki brid između $a$ i $b$ težine $w$ `tuple` oblika ($w$, $a$, $b$). Tu ćemo listu sortirati po težinama kako bi se algoritam pravilno provodio. 56 | 57 | ```cpp 58 | vector> mst; 59 | for(auto &edge : edgeList) { 60 | int a, b; 61 | tie(ignore, a, b) = edge; 62 | 63 | if(!same(a, b)) { 64 | mst.push_back(edge); 65 | unite(a, b); 66 | } 67 | } 68 | ``` 69 | 70 | :::noteprimijetite 71 | 72 | Složenost ovog algoritma je $O(m \log m)$ zbog sortiranja, gdje je $m$ broj bridova. 73 | 74 | ::: 75 | -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/poslovi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Raspored poslova" 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | ### Problem 4: Raspored poslova 10 | 11 | Zadan je broj $k$. 12 | U sljedećih $k$ redova zadano je po dva broja, $a_i$ i $b_i$, 13 | koji označavaju početno i krajnje vrijeme posla $i$. 14 | Samo jedan posao se može obavljati u isto vrijeme. 15 | Posao se obavlja u vremenskom intervalu $[a_i, b_i]$ (uključno). 16 | 17 | Vrijedi $1 \leq k \leq 10^6$, $0 \leq a_i, b_i \leq 10^9$. 18 | 19 | Koliko je najviše poslova moguće obaviti? 20 | 21 | #### Testni primjeri 22 | 23 | Pokušajte razviti pohlepni pristup i iskušajte ga na sljedećim primjerima: 24 | 25 | ```shell 26 | 3 27 | 1 5 28 | 2 2 29 | 3 10 30 | ``` 31 | Rješenje: 2 32 | 33 | ```shell 34 | 3 35 | 1 3 36 | 2 2 37 | 3 3 38 | ``` 39 | Rješenje: 2 40 | 41 | Pokušajte dokazati optimalnost vašeg pristupa! 42 | 43 | #### Pohlepni pristup: 44 | 45 | Poslove je potrebno sortirati prema vremenu završetka (od najmanjeg prema najvećem). 46 | Zatim je potrebno izvršavati poslove redom, i paziti da se ne preklapaju s već izvršenima. 47 | 48 | ```cpp 49 | #include 50 | using namespace std; 51 | 52 | int main(){ 53 | int k; cin>>k; 54 | 55 | vector> P(k); 56 | for (int i=0; i>a>>b; 58 | P[i] = {a,b}; 59 | } 60 | 61 | sort(P.begin(), P.end(), 62 | [](auto p1, auto p2) -> bool{return p1.second < p2.second;} 63 | ); 64 | 65 | int last_time = -1; 66 | int cnt = 0; 67 | 68 | for (auto p : P){ 69 | if (p.first > last_time){ 70 | cnt++; 71 | last_time = p.second; 72 | } 73 | } 74 | 75 | cout << cnt << endl; 76 | } 77 | ``` 78 | 79 | #### Dokaz: 80 | 81 | Gotovo svaki dokaz optimalnosti pohlepnog algoritma $A$ počinje sa tezom: 82 | - pretpostavimo da dolazimo do *nekog* optimalnog rješenja koji **ne bi pronašao** algoritam $A$. 83 | 84 | Izraz *nekog* bitan je zbog mogućnosti postojanja više optimalnih rješenja. 85 | Izraz "**ne bi pronašao** algoritam $A$" u kombinaciji s definicijom $A$ implicira da 86 | je u rješenju postoji prvo odudaranje od algoritma $A$, to jest trenutak kad smo mogli 87 | izabrati ono što bi birao algoritam $A$, a nismo. 88 | 89 | Na primjer, rješenje je lista poslova koja počinje listom $X_1$, zatim poslom $x$, a završava listom $X_2$. 90 | Posao $x$ označava trenutak kad smo mogli odabrati posao $z$ (kojeg bi odabrao $A$), a nismo. 91 | 92 | Po definiciji taj posao $z$ smo mogli odabrati, a završava prije $x$. 93 | Zbog toga je lista $X_1$ - $z$ - $X_2$ isto tako dobra lista poslova ($z$ ne smeta poslovima 94 | poslije jer završava prije $x$). Dakle, uzimanjem posla $z$ umjesto $x$ dobivamo 95 | barem jednako dobro rješenje. 96 | 97 | Zaključak je da koje god rješenje imamo, postupanje pohlepnog algoritma nam daje bar jednako dobro rješenje 98 | kao što imamo - pohlepni algoritam je optimalan. 99 | 100 | #### Apstraktni pregled dokaza 101 | 102 | Dokaz se svodi na razmatranje rješenja koje odudara od pohlepnog algoritma, 103 | a zatim pokazivanje da se prelaskom na odluke koje bi činio pohlepni algoritam 104 | dobiva barem jednako dobro rješenje. 105 | 106 | Pokušajte sljedeći [problem](https://codeforces.com/problemset/problem/1238/B) riješiti (i dokazati rješenje) na sličan način. Razmotrite vaš postupak u svakom koraku, i razmislite postoji li neki 107 | koji je uvijek barem jednako dobar. 108 | 109 | -------------------------------------------------------------------------------- /docs/algoritmi-nad-grafovima-2/2SAT.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2SAT 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Notacija 12 | 13 | $\lnot$ jest logička operacija NOT (NE). 14 | 15 | $\land$ jest logička operacija AND (I). 16 | 17 | $\lor$ jest logička operacija OR (ILI). 18 | 19 | $\equiv$ ili $\iff$ označava logičku ekvivalenciju. 20 | 21 | $\Longrightarrow$ predstavlja logičku implikaciju. 22 | 23 | ### Uvod 24 | 25 | **SAT** (*Boolean satisfiability problem*) jest problem pridruživanja istine ili laži varijablama tako da se zadovolji neka Booleova funkcija zapisana u kanonskom obliku produkta maksterma. To se preciznije naziva konjunktivna normalna forma. 26 | 27 | **2-SAT** poseban je slučaj SAT-a kada se svaki maksterm sastoji od dvije varijable. 28 | 29 | $$ 30 | \begin{aligned} 31 | &(x_0\lor x_2)\land(x_0\lor\lnot x_3)\land(x_1\lor\lnot x_3)\land(x_1\lor\lnot x_4)\land{} \\ 32 | &(x_2\lor\lnot x_4)\land{}(x_0\lor \lnot x_5)\land (x_1\lor\lnot x_5)\land (x_2\lor\lnot x_5)\land{} \\ 33 | &(x_3\lor x_6)\land (x_4\lor x_6)\land (x_5\lor x_6) 34 | \end{aligned} 35 | $$ 36 | 37 | Iznad je napisan primjer funkcije koju je potrebno zadovoljiti. Cilj nam je dodijeliti varijablama $x_i$ vrijednosti istinu ili laž, tako da izraz naposljetku bude istinit. Primjetimo da to znači da u svakoj zagradi barem jedan operand operacije $ili$ mora biti istinit. Ako je taj operand oblika $x_i$, tada mora vrijediti $x_i = 1$. Ako pak je taj operand oblika $\lnot x_i$, onda mora biti $x_i = 0$. 38 | 39 | Znamo: 40 | 41 | $$ 42 | (x_0\lor\lnot x_3) \;\equiv\; (\lnot x_0\Rightarrow\lnot x_3) \;\equiv\; (x_3\Rightarrow x_0) 43 | $$ 44 | 45 | Dakle možemo konstruirati usmjereni graf s po dva vrha za svaku varijablu: jedan za nju samu, i još jedan za njezin komplement. Povlačimo brid iz jednog vrha u drugi kada vrijednost prvog vrha implicira vrijednost drugog vrha u zadanoj formuli. Na donjoj je slici konstruiran takav graf za gore navedeni primjer. 46 | 47 | ![Graf konstruiran pomoću zadane funkcije](/img/implicationGraph.png) 48 | 49 | ### Rješenje 50 | 51 | 2SAT problem ima rješenje ako i samo ako njegov zapis kao gore definirani graf s implikacijama nema $x_i$ i $\lnot x_i$ u istoj strogo povezanoj komponenti, za svaku varijablu $x_i$. 52 | 53 | U protivnom postoji barem jedno rješenje, koje je moguće dobiti sljedećim postupkom: 54 | 55 | - Konstruiramo usmjereni graf $G$ kao što je gore opisano 56 | - Odredimo strogo povezane komponente grafa 57 | - Kondenziramo graf, odnosno u $G$ svaku strogo povezanu komponentu stegnemo da dobijemo $G'$, u kojem svaki vrh predstavlja jednu strogo povezanu komponentu iz $G$. U grafu $G'$ postoji brid iz $u'$ u $v'$ ako postoji brid $uv$ u $G$ takav da je $u \in u'$ i $v \in v'$. 58 | - Topološki sortiramo vrhove u $G'$ i rješenje zapišemo u neku listu $L$ 59 | - Obrnutim redoslijedom prolazimo kroz $L$ i obrađujemo komponentu po komponentu: 60 | - Svi vrhovi u komponenti moraju biti iste logične vrijednosti, znači ili su svi istiniti ili su svi lažni. 61 | - Ako barem jedan vrh nema pridruženu logičnu vrijednost, tada ćemo svim vrhovima u komponenti pridružiti istinu. Ako u nekom vrhu piše $x_i$, postavljamo $x_i = 1$. Ako pak u nekom vrhu piše $\lnot x_i$, tada postavljamo $x_i = 0$. Time postižemo da je svaki vrh u komponenti istinit, pa ne može doći do lažne implikacije $1 \Rightarrow 0$. 62 | - Ovo smo radili obrnutim topološkim redoslijedom jer, ako postoji put od komponente $X$ do komponente $Y$, želimo prvo postaviti komponentu $Y$ da bude istinita. Ovime sprječavamo da dođe do lažne implikacije $1 \Rightarrow 0$. -------------------------------------------------------------------------------- /docs/dinamicko-programiranje/knapsack.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Knapsack 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Problem 12 | 13 | Ruksak kapaciteta $x$ moramo napuniti sa stvarima kojih ima $n$, a od kojih svaka zauzima određeni kapacitet i ima određenu vrijednost. Naravno cilj nam je maksimizirati ukupnu vrijednost stvari u ruksaku. U zadatku bitnu razliku čini jesu li stvari tekuće(možemo odabrati proizvoljnu količinu svake stvari) ili čvrste(moramo uzeti cijelu stvar). 14 | 15 | ### Rješenje 1 16 | 17 | Ako su stvari tekuće onda pohlepno možemo uzimati stvari što većeg omjera vrijednosti naspram količine. 18 | 19 | Ako su stvari čvrste tada nam isti pohlepni pristup neće dati točno rješenje i to je jednostavno pokazati. Neka je kapacitet ruksaka $10$ i imamo $3$ stvari oblika $\{size, value\}$: $\{7, 7\}, \{5, 4\}, \{5, 4\}$. Pohlepni pristup uzeo bi stvar vrijednosti $7$ i više ništa ne bi stalo u ruksak, a bolja opcija je uzeti ostale dvije stvari i postići vrijednost $8$. 20 | 21 | Neka nam je stanje određeno s 2 parametra, broj stvari koje koristimo i veličina ruksaka. Prijelaz radimo tako da odaberemo stvar koja stane u trenutnu veličinu i smanjimo veličinu ruksaka za veličinu te stvari ili jednostavno ne odaberemo tu stvar. Kod je jednostavno napisati rekurzivno, naravno koristeći memoizaciju. 22 | 23 | ```cpp 24 | const int MAXN = 100; 25 | const int MAXX = 100000; 26 | int x, n; 27 | pair stvari[MAXN]; 28 | //bitno je paziti može li ukupna vrijednost biti veca od integera 29 | //takoder, ako su stvari tekuce rješenje može biti decimalni broj 30 | long long int memo[MAXN][MAXX]; 31 | 32 | long long int solve(int items, int cap){ 33 | if(items < 0) return 0; 34 | if(memo[items][cap] != -1) return memo[items][cap]; 35 | if(stvari[items].first > cap) return memo[items][cap] = solve(items - 1, cap); 36 | return memo[items][cap] = max(solve(items - 1, cap), stvari[items].second + solve(items - 1, cap - stvari[items].first));; 37 | } 38 | 39 | int main(){ 40 | memset(memo, -1, sizeof memo); 41 | cin >> n >> x; 42 | for(int i = 0; i < n; i++) cin >> stvari[i].first >> stvari[i].second; 43 | cout << solve(n - 1, x) << endl; 44 | return 0; 45 | } 46 | ``` 47 | 48 | ### Rješenje 2 49 | 50 | Neka nam je stanje najveća vrijednost koju možemo ostvariti za neku vrijednost $a$. Funkcija prijelaza je $val[a] = max(val[a], val[a - velicina[i]] + vrijednost[i])$ za svaki $i$. 51 | 52 | ```cpp 53 | const int MAXX = 100000; 54 | int x, n; 55 | int vrijednost, velicina; 56 | //bitno je paziti može li ukupna vrijednost biti veća od integera 57 | //također, ako su stvari tekuće rješenje može biti decimalni broj 58 | long long int dp[MAXX]; 59 | 60 | int main(){ 61 | memset(dp, 0, sizeof dp); 62 | cin >> n >> x; 63 | for(int i = 0; i < n; i++){ 64 | cin >> velicina >> vrijednost; 65 | //ako for petlja ide od 0 tada bi se moglo dogoditi da istu stvar stavimo u ruksak više puta 66 | for(int j = x - velicina; j >= 0; j--){ 67 | dp[j + velicina] = max(dp[j + velicina], dp[j] + vrijednost); 68 | } 69 | } 70 | cout << dp[x]; 71 | return 0; 72 | } 73 | ``` 74 | 75 | ### Analiza složenosti 76 | 77 | U slučaju tekućih stvari je potrebno sortirati prema omjeru vrijednosti naspram količine, nakon toga moguće je linearno riješiti zadatak. Pošto je sortiranje složenosti $O(n \log n)$ to je i složenost rješenja. 78 | 79 | U slučaju čvrstih stvari složenost oba rješenja je $O(n * w)$, no primijetimo da je u prvom rješenju memorijska složenost $O(n * w)$, dok je u drugom samo $O(w)$; -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/meet-in-the-middle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Meet in the middle 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | **Meet in the middle** (*MITM*) skup je tehnika kojima prostor pretraživanje krenemo pretraživati 10 | s obe strane. Na primjer, traženje najkraćeg puta može istodobno krenuti od odredišta i cilja. 11 | 12 | 13 | ### Problem 1: K-sum (MITM) 14 | 15 | Ideja korištenja MITM tehnike nad ovim problemom dolazi iz činjenice da bismo 16 | brojeve mogli podijeliti na $2$ skupa, $A$ i $B$, brojevima iz skupa $A$ 17 | *povećavati nulu*, a brojevima iz skupa $B$ *smanjivati $x$*. 18 | 19 | Iako je ovo dobra formulacija rješenja, pretraga bi uključivala traženje 20 | svih stanja (zbrojeva) koji se mogu dobiti iz $A$ - ovo stanja nazovimo $S_A$. 21 | S druge strane, sva reduciranja $x$-a brojevima iz B rezultiraju stanjima koja 22 | nazivamo $S_B$. Na kraju moramo pronaći broj $p$ koji postoji i u $S_A$ i u $S_B$. 23 | 24 | Na primjer, ako brojeve $\{3,5,7\}$ zajedno sa ciljem 10 podijelimo u skupove 25 | $A = \{3,5\}$ i $B = \{7\}$, rezultatna stanja su 26 | $S_A = \{0,3,5,8\}$ i $S_B = \{10,3\}$. Budući da je broj 3 nalazimo u oba skupa stanja, 27 | odgovor na problem je "DA". 28 | 29 | Traženje preklapanja možemo ostvariti sortiranjem jednog od skupova stanja ($S_A$), 30 | a zatim iteriranjem po elementima $S_B$, kojima tražimo para u $S_A$ binarnom pretragom. 31 | 32 | ```cpp 33 | #include 34 | using namespace std; 35 | 36 | int main(){ 37 | int n,x; 38 | cin >> n; 39 | int a = n/2; 40 | int b = n-a; 41 | 42 | // pola u A 43 | vector A(a); 44 | for (int i=0;i>A[i]; 46 | } 47 | 48 | // pola u B 49 | vector B(b); 50 | for (int i=0;i>B[i]; 52 | } 53 | cin >> x; 54 | 55 | // izracunaj i zapamti S_A (from0) 56 | vector from0; 57 | for(int i=0; i < (1< 12 | 13 | Hint 1 14 | 15 |
16 | Zamislite da u zadatku imate samo popis godina rođenja svih mogućih ljudi na tom planetu i pitanje je koliko je ljudi rođeno PRIJE neke godine? Da bismo mogli efikasno odgovarati na ovakve upite (koristeći binarnu pretragu), potrebno je prvo SORTIRATI dobiveni niz godina. 17 |
18 | 19 | 20 |
21 | 22 | Hint 2 23 | 24 |
25 | Koje sve godine mogu biti rješenje? Ako malo razmislite, primijetit ćete da su jedino godine kada se netko rodio / umro moguća rješenja upita (broj mogućih rješenja 'odgovara' broju ljudi). Drugim riječima, ako je neka godina u kojoj nije bilo promjene broja stanovnika rješenje, onda je sigurno i prethodna godina rješenje, ali njoj dajemo prednost zbog uvjeta zadatka koji traži minimalnu godinu. 26 |
27 |
28 | 29 |
30 | 31 | Hint 3 32 | 33 |
34 | Iako nam je prirodno vezati godinu rođenja i godinu smrti uz pojedinu osobu, važno je primijetiti da u ovom zadatku ta dva podatka ne trebaju biti povezana i da ih možemo gledati neovisno. 35 |
36 |
37 | 38 |
39 | 40 | Rješenje 41 | 42 |
43 | Jedan od pristupa je da pri unosu podataka skupimo sve godine koje su moguće rješenje zadatka i potom za svku provjeravamo koliko je ljudi tada bilo živo (pamtimo samo godinu s maksimalnim brojem i broj ljudi koji su živjeli te godine). 44 | Recimo da nas zanima koliko je ljudi bilo živo 1996. Taj podatak možemo odrediti ako od svih ljudi koji su se rodili prije 1996. oduzmemo sve one koji su umrli prije 1996. Prvi član u razlici dobivamo binarnom pretragom po godinama rođenja, a drugi pretragom po godinama smrti. Ukupna složenost algoritma je O(n logn) 45 |
46 |
47 | 48 | ### Zadatak: The Parade 49 | 50 | Tekst zadatka: [The Parade](https://codeforces.com/problemset/problem/1250/J) 51 | 52 |
53 | 54 | Hint 1 55 | 56 |
57 | Što ako odaberemo neku veličinu reda r i pitamo se možemo li napraviti k redova veličine r? U kojoj složenosti možemo izračunati taj podatak? 58 |
59 |
60 | 61 |
62 | 63 | Hint 2 64 | 65 |
66 | Prethodna se provjera može napraviti u linearnoj složenosti (O(n)). Neka su brojevi vojnika redom 7, 3 i 2. Pokušajmo napraviti redove veličine 3. Od prvih 7 vojnika visine 1 možemo napraviti dva reda veličine 3 (ostao je jedan vojnik). Tada tog jednog pokušamo ubaciti u idući red zajedno s vojnicima visine 2. Napravili smo red [1,2,2] i preostao je jedan vojnik visine 2. Sada spajamo tog preostalog vojnika s 2 vojnika visine 3 i dobili smo red [2,3,3]. 67 |
68 |
69 | 70 |
71 | 72 | Rješenje 73 | 74 |
75 | Time limit je 2s pa dozvoljen broj operacija redom veličine odgovara 10^8. Budući da je u zadatku do 10^4 test-primjera, ostatak se koda također treba izvršavati u složenosti O(10^4). Uočite da postoji neka granična veličina reda, tj. maksimalna moguća s kojom možemo napraviti k redova. Ideja je da binary-searchamo tu veličinu reda (r) i za svaku moguću u linearnoj složenosti provjeravamo je li moguće složiti k redova. Ako nije, pretražujemo po nižim veličinama, a ako je, pretražujemo po višima. Konačno rješenje je onda r*k. 76 |
77 |
78 | 79 | ### Ostali zadaci 80 | 81 | - [Sorted Adjacent Differences](https://codeforces.com/problemset/problem/1339/B) 82 | - [Worms](https://codeforces.com/problemset/problem/474/B) 83 | -------------------------------------------------------------------------------- /docs/algoritmi-nad-grafovima-1/union-find-struktura.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Union-find struktura 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Zadatak 12 | 13 | Zamislite da u nekoj zemlji postoji $n$ gradova i $m$ cesta koje ih povezuju, a u svakom gradu na početku živi $x$ ljudi. Tijekom vremena, neke ceste postaju nestabilne i urušavaju se. U tom svijetu provodite 2 oblika queryja: 14 | 15 | - D **K** - urušila se cesta **K** 16 | - P **A x** - populacija **A**-tog grada postala je **x** 17 | 18 | Vi ste mladi nadobudni geograf i želite nakon svakog upita odgovoriti koji je broj stanovnika trenutno najnaseljenije regije. Regija je definirana kao podskup gradova u kojem se može doći cestom između bilo koja dva. 19 | 20 | Ovaj je zadatak ovdje samo za ilustraciju, ali probajte malo razmisliti kako biste ga riješili. Puni tekst problema možete naći [ovdje](https://www.codechef.com/problems/ABROADS). 21 | 22 | ## Struktura 23 | 24 | Union find (nekad i disjoint set) je struktura podataka koja elemente raspodjeljuje u **skupove**. Za dva skupa kažemo da su **disjunktni** (disjoint) ako ne postoji element koji pripada u oba skupa. Struktura je napravljena tako da svaki skup ima svoga predstavnika (representative), a elementi skupa su preko lanca povezani s predstavnikom. Na slici ispod su prikazana tri skupa, {1, 4, 7}, {5} i {2, 3, 6, 8}. Predstavnici skupova su redom 4, 5 i 2. 25 | 26 | union_find 27 | 28 | Predstavnike također možemo gledati na razini lanca. Npr. reći ćemo da elementi 6 i 8 imaju za predstavnika element 3. 29 | 30 | Union find podržava dvije operacije: 31 | 32 | - **unite($a$,$b$)** - operacija koja spaja skup koji sadrži element $a$ sa skupom koji sadrži element $b$ 33 | - **find($x$)** - operacija koja traži predstavnika skupa koji sadrži element $x$ 34 | 35 | :::importantbitno 36 | 37 | Obje operacije rade u logaritamskoj složenosti. 38 | 39 | ::: 40 | 41 | Dva skupa možemo spojiti tako da povežemo njihove predstavnike. Na slici ispod spojili smo skupove {1, 4, 7} i {2, 3, 6, 8} tako da 2 postaje novi predstavnik, a skup koji u konačnici dobivamo je {1, 2, 3, 4, 6, 7, 8}. 42 | 43 | union_find2 44 | 45 | Pokazuje se da je prilikom spajanja uvijek **efikasnije spajati manji skup na veći**. Na taj je način naš skup više 'razgranat' i možemo postići logaritamsku složenost pretraživanja. 46 | 47 | ## Implementacija 48 | 49 | Za ovu ćemo implementaciju koristiti 1D polje `link` gdje će na i-tom mjestu pisati _predstavnik i-tog elemenata_ te polje `len` gdje ćemo na mjestima na kojima su predstavnici pisati _veličina skupa kojeg predstavljaju_. Na gornjoj bi slici bilo `link[7]=4` i `len[2]=7`. 50 | 51 | Na početku svaki element pripada svom skupu pa je i sam svoj predstavnik. Duljina svih skupova je $1$. 52 | 53 | ```cpp 54 | for(int i=0; i 10 | 11 | ### Problem 12 | 13 | Zadano je $N$ jednadžbi oblika $x \equiv a_i(\textrm{mod}\ b_i)$. Značenje ove jednadžbe je da $x$ ima ostatak $a_i$ pri dijeljenju s $b_i$. Nađi najmanji $x$ koji zadovoljava sve jednadžbe, ako takav broj ne postoji ispiši $-1$. 14 | 15 | ### Rješenje 16 | 17 | Rješenje koje se prvo nameće je *brute force* provjera svih brojeva dok ne nađemo prvi koji zadovoljava jednadžbe. Primijetimo da, u ovisnosti o ulaznim podatcima, rješenje može biti jako veliko, također rješenje ne mora postojati. Stoga *brute force* nije najsretnija opcija. 18 | 19 | Probajmo riješiti problem ako imamo samo dvije jednadžbe. Neka su jednadžbe: 20 | 1. $x \equiv 3(\textrm{mod}\ 5)$ 21 | 2. $x \equiv 4(\textrm{mod}\ 6)$ 22 | 23 | Primijetimo da ne moramo prolaziti sve brojeve nego samo one koji odgovaraju prvoj jednadžbi te njih možemo uspoređivati s drugom jednadžbom kako bi našli najmanje rješenje. Znamo da je rješenje prve jednadžbe oblika $k * b_1 + a_1$, odnosno $5k + 3$ te možemo početi od $k = 0$ sve dok ne nađemo neki $k$ koji zadovoljava obje jednadžbe. Rješenje dobivamo za $k = 5$, odnosno prvi broj koji zadovoljava obje jednadžbe je $x = 28$. 24 | 25 | Dodajmo treću jednadžbu: $x \equiv 0(\textrm{mod}\ 8)$. 26 | 27 | Sada moramo prolaziti brojeve koji zadovoljavaju obje prve jednadžbe te ih uspoređivati s novom jednadžbom. Primijetimo da sve te brojeve možemo zapisati pomoću nove jednadžbe. U ovom slučaju ta jednadžba je $30x + 28$. Do te jednadžbe možemo doći dodavanjem $a_1$ ili $a_2$ početnom broju tražeći sljedeću vrijednost koja zadovoljava obje jednadžbe, no taj postupak može biti spor. Primijetimo da će faktor uz $x$ dijeliti i $b_1$ i $b_2$, a pošto bi htjeli najmanji takav broj dovoljno je naći $LCM(b_1, b_2)$. Sada smo problem ponovno sveli na samo dvije jednadžbe, a taj smo problem riješili gore pa ćemo primijeniti isti postupak. 28 | 29 | 1. $x \equiv 28(\textrm{mod}\ 30)$ 30 | 2. $x \equiv 0(\textrm{mod}\ 8)$ 31 | 32 | Krećemo od $28$ i dodajemo 30 sve dok ne pronađemo prvi broj koji zadovoljava drugu jednadžbu. 33 | 34 | 0. $k = 0$, $x = 28$, $28 \equiv 4(\textrm{mod}\ 8)$ 35 | 1. $k = 1$, $x = 58$, $58 \equiv 6(\textrm{mod}\ 8)$ 36 | 2. $k = 2$, $x = 88$, $88 \equiv 0(\textrm{mod}\ 8)$ 37 | 38 | Postavlja se pitanje kako ćemo otkriti ako rješenje ne postoji. Odgovor je vrlo jednostavan, ako dva puta naiđemo na isti ostatak tada znamo da rješenje ne postoji. Ako rješenje ne postoji prvo ćemo naići upravo na prvi ostatak jer se ostatci ponašaju ciklično. 39 | 40 | ```cpp 41 | int n; 42 | pair jed[MAXN]; 43 | pair cur; 44 | long long int sol; 45 | 46 | int LCM(int a, int b){ 47 | /*znamo od prije*/ 48 | } 49 | 50 | int main(){ 51 | cin >> n; 52 | for(int i = 0; i < n; i++) cin >> jed[i].first >> jed[i].second; 53 | cur = jed[0]; 54 | for(int i = 1; i < n; i++){ 55 | bool flag = true; 56 | for(int k = 0; k < jed[i].second; k++){ 57 | if((k * cur.second + cur.first) % jed[i].second == jed[i].first){ 58 | cur.first = k * cur.second + cur.first; 59 | cur.second = LCM(jed[i].second, cur.second); 60 | flag = false; 61 | break; 62 | } 63 | } 64 | if(flag){ 65 | cout << -1; 66 | return 0; 67 | } 68 | } 69 | cout << cur.first; 70 | } 71 | ``` 72 | 73 | ### Analiza složenosti 74 | 75 | Pošto postoji $N$ jednadžbi, a mi postupno zamjenjujemo dvije jednadžbe s jednom novom, to ćemo ponoviti $N - 1$ puta. Svaka zamjena je složenosti $O(a_i + log(a_i))$ jer ćemo proći najviše $a_i$ brojeva da nađemo onaj koji nam odgovara ili da shvatimo da nema rješenja, a nakon toga ćemo računati $LCM$, no ta je složenost manja pa ju možemo zanemariti. Stoga je ukupna složenost $O(\sum_{i=1}^n(a_i))$, naravno to možemo zapisati i kao $O(N * max(a_i))$. 76 | -------------------------------------------------------------------------------- /docs/doprinos-ovim-materijalima/kako-napisati-clanak.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pisanje članka" 3 | --- 4 | 5 | ## Pisanje članaka 6 | 7 | Svatko može doprinijeti ovim materijalima pisanjem novih ili prepravljanjem starih članaka. 8 | 9 | Potrebno 10 | - git klijent (upute su za git command-line interface) 11 | - github account (opcionalno, samo za pull-request način) 12 | - npm (node package manager) (opcionalno, samo ako želite vidjeti rezultat, ali preporučeno). 13 | 14 | ### Korak 1: preuzimanje repozitorija i pokretanje servera 15 | 16 | Pozicionirajte se na mjesto gdje želite klonirati repozitorij. 17 | Sa naredbom `git clone https://github.com/x-fer/natpro` 18 | klonirajte repozitorij. 19 | 20 | Ako želite uživo vidjeti rezultate vaših modifikacije, pokrenite 21 | `npm install` , a zatim `npm start`. 22 | Zadnja komanda pokrenut će server koji prati promjene i automatski ažurira stranicu. 23 | Stranicu možete vidjeti na `localhost:3000`. 24 | 25 | ### Korak 2: doprinos 26 | 27 | Ugasite server ako je upaljen. 28 | Napravite novu granu i pozicionirajte se na nju sa naredbom `git checkout -b ime-prezime-tema`, 29 | npr. `petar-mihalj-pohlepni-algoritmi`. 30 | Sad ponovo možete upaliti server. 31 | 32 | Jedini folderi koje biste trebali mijenjati su `static` i `docs`, uz file `sidebars.js`. 33 | Namjena svih trebala bi biti jasna, a primjere markdown sintakse možete vidjeti u `docs/doprinos-ovim-materijalima/upute-za-markdown`. 34 | 35 | Dok radite na materijalima korisno je dijelove doprinosa odvajati u različite commit-ove, 36 | ali strukturu tih prepuštamo autoru, dok god krajnji rezultat bude u commitu koji je na inicijalno napravljenom branchu. 37 | 38 | ### Korak 3: stvaranje commita 39 | 40 | Nakon završetka doprinosa autor bi trebao commitati sve promjene koje ima. 41 | Naredba `git status` trebala bi pokazati samo datoteke koje autor mijenja. 42 | Autor zatim treba pripremiti commit dodavanjem svih promijenjenih datoteka koje su bitne za sadržaj, 43 | uzastopnom primjenom naredbi `git add ` ili `git add .` (druga samo ako status pokazuje točno ono što treba dodati). 44 | Na kraju treba završiti commit naredbom `git commit -m "Poruka koja opisuje što je napravljeno"`. 45 | Poruka može biti i deskriptivnija. 46 | 47 | ### Korak 4: predaja 48 | 49 | Izaberite jednu od ponuđenih metoda predaje članka. 50 | 51 | #### Korak 4a (nije potreban github account): 52 | 53 | Prva metoda slanja rezultata svodi se na slanje *.patch* datoteka na oba maila: 54 | 55 | - `ivan.vlahov@gmail.com` 56 | - `git@pmihalj.com` 57 | 58 | Patch datoteke možete generirati naredbom `git format-patch $(git merge-base --fork-point main) --stdout > ime-prezime-tema.patch`. 59 | Ta naredba generirat će `.patch` datoteku koja sadrži sve promjene od trenutka kad se trenutni branch odvojio od `main` brancha. 60 | Datoteku je potrebno poslati na mailove koji su već navedeni. 61 | 62 | #### Korak 4b (potreban github account): 63 | 64 | Druga metoda slanja svodi se na upload novih/uređenih datoteka na `pastebin.com` i otvaranje github issue-a. 65 | 66 | Odite na github, otvorite issue na `x-fer/natpro` repozitoriju, opišite što ste napravili i pošaljite linkove na datoteke. 67 | Urednici će promjene razmotriti i eventualno ugraditi u stranicu. 68 | 69 | #### Korak 4c (potreban github account): 70 | 71 | Treća metoda slanja svodi se na forkanje repozitorija i pushanje podataka na svoj repozitorij, 72 | a zatim otvaranje pull requesta na `x-fer/natpro` repozitoriju. 73 | 74 | U github sučelju u desnom gornjem kutu postoji `fork` gumb. Njim kreirate kopiju 75 | natpro repozitorija na vašem github računu. 76 | 77 | Nakon što to napravite, trebate poslati branch koji ste napravili lokalno, 78 | upravo na taj repozitorij. Za početak promijenite referentni remote na vaš remote. 79 | 80 | Uklonite postojeći: 81 | `git remote rm origin` 82 | 83 | a) Ako koristite username i password za autorizaciju: 84 | `git remote add origin https://github.com/GITHUB_ACCOUNT/natpro` 85 | 86 | b) Ako koristite ssh ključ: 87 | `git remote add origin git@github.com:GITHUB_ACCOUNT/natpro` 88 | 89 | Pošaljite branch na svoj remote. 90 | `git branch --set-upstream-to origin/ime-prezime-tema` 91 | `git push` 92 | 93 | Napravite pull request kojim targetirate taj branch na remoteu. 94 | (ovo treba malo dopisati) 95 | 96 | ### Hvala! 97 | 98 | Vaš doprinos će se razmotriti, a ako je dobar, uključit će se u materijale! 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/matematika/multiplikativni-inverz.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multiplikativni inverz 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | **Modularni multiplikativni inverz** cijelog broja $a$ modulo $m$ je cijeli broj $a^{-1}$ koji zadovoljava kongruenciju $a \cdot a^{-1} \equiv 1 \ (\textrm{mod}\ m)$. 12 | 13 | Na primjer, $6^{-1} \equiv 3 \ (\textrm{mod}\ 17)$, jer je $6 \cdot 3 \equiv 1 \ (\textrm{mod}\ 17)$. Primijetimo da je inverz različit za različite $m$, i da ne postoji uvijek (pokušajte ga pronaći za $a=2$, $m=4$), a može se pokazati da postoji ako i samo ako su $a$ i $m$ relativno prosti. Multiplikativni inverzi korisni su u modularnom dijeljenju - dijeljenje brojem $a$ modulo $m$ odgovarat će množenju s $a^{-1}$, kao i u običnoj aritmetici. 14 | 15 | Jedan način za njihovo računanje koristi Eulerov teorem. 16 | 17 | ### Eulerov teorem 18 | 19 | **Eulerova funkcija $\varphi(n)$**, engl. _Euler's Totient Function_, daje broj prirodnih brojeva izmedu $1$ i $n$ koji su relativno prosti s $n$. Npr., $\varphi(10)=4$, jer su 1, 3, 7 i 9 relativno prosti s 10. Primijetimo da će za proste $n$ vrijediti $\varphi(n)=n-1$. 20 | 21 | Neka su $p_i$ prosti brojevi iz rastava $n$ na proste faktore, i neka ih ima $k$. Tada se vrijednost Eulerove funkcije može dobiti formulom $\varphi(n) = n\prod_{i=1}^{k}\left(1-\frac{1}{p_i}\right)$. Sljedeći algoritam implementira ovu formulu u složenosti $O(\sqrt{n})$: 22 | 23 | ```cpp 24 | int phi(int n) { 25 | int rez = n; 26 | for (int i = 2; i * i <= n; ++i) { 27 | /* Provjeravamo je li n djeljiv s i. Ako jest, onda je i prost broj iz njegovog rastava. */ 28 | /* Kako znamo da je i prost? Počinjemo s prostim brojem 2. Za trenutni i, u while petlji ispod */ 29 | /* ćemo 'ukloniti' sva javljanja faktora i iz n, pa svaki sljedeći i koji dijeli (promijenjeni) n */ 30 | /* neće biti višekratnik trenutnog i. Tako svaki trenutni i nije višekratnik brojeva 2..i-1, pa je prost. */ 31 | if (n % i == 0) { 32 | while (n % i == 0) 33 | n /= i; 34 | /* rez = rez * (1 - 1/i) = rez - rez/i */ 35 | rez -= rez / i; 36 | } 37 | } 38 | /* Ako je n>1, onda postoji prost faktor od n veći od sqrt(n). */ 39 | /* Takav može biti samo jedan. Dokaz: pretp. da postoje dva prosta faktora a,b>sqrt(n). */ 40 | /* Tada i a*b dijeli n, pa je a*b<=n. Ali, sqrt(n)*sqrt(n) 1) 43 | rez -= rez / n; 44 | return rez; 45 | } 46 | ``` 47 | 48 | Kako je Eulerova funkcija vezana za računanje multiplikativnih inverza, postat će jasnije uvođenjem **Eulerovog teorema**. On kaže da, za relativno proste prirodne brojeve $a$ i $m$, vrijedi $a^{\varphi(m)} \equiv 1 \ (\textrm{mod}\ m)$. 49 | 50 | Množenjem obiju strana gornje kongruencije s $a^{-1}$, dobivamo $a^{\varphi(m)-1} \equiv a^{-1} \ (\textrm{mod}\ m)$. Za prost $m$, izraz se pojednostavljuje na $a^{m-2} \equiv a^{-1} \ (\textrm{mod}\ m)$. 51 | 52 | ### Računanje multiplikativnog inverza 53 | 54 | Korištenjem Eulerovog teorema, algoritma za brzo potenciranje objašnjenog u poglavlju Važne formule, i gornje funkcije _phi_, možemo lako izračunati multiplikativni inverz broja $a$ modulo $m$: 55 | 56 | ```cpp 57 | int multiplikativni_inverz(int a, int m){ 58 | return brzo_potenciranje(a, phi(m)-1, m); 59 | } 60 | ``` 61 | 62 | Ako je vrijednost _phi(m)_ unaprijed poznata, npr. kada je $m$ prost i vrijednost funkcije $m-1$, složenost ove metode određena je funkcijom _brzo_potenciranje_ i iznosi $O(\log{m})$. U nekim slučajevima, poput računa modulo $1e9 + 7$, ovaj pristup može biti dovoljno dobar. 63 | 64 | Ako se radi o računu modulo $m$ gdje je $m$ složen broj, a vrijednost Eulerove funkcije _phi(m)_ nije unaprijed poznata, potrebno je računati i nju što može biti vremenski zahtjevno. Bolji pristup koristio bi prošireni Euklidov algoritam, ili računao inverze za niz brojeva odjednom kao ispod. Razumijevanje tih algoritama traži dodatno poznavanje teorije brojeva, ali same formule i implementacija nisu teški. Više o njima možete saznati ovdje. 65 | 66 | ```cpp 67 | /* računanje inverza svih brojeva u intervalu [1, m-1] */ 68 | inv[1] = 1; 69 | for(int i = 2; i < m; ++i) 70 |     inv[i] = m - (m/i) * inv[m%i] % m; 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/algoritmi-nad-grafovima-2/kosarajuev-algoritam.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kosarajuev algoritam 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Definicija 12 | 13 | Za neki usmjereni graf kažemo da je **strogo povezan** (engl. *strongly connected*) ako postoji put između svaka dva vrha. Ako su *u* i *v* različiti vrhovi, tada postoji put od *u* do *v* te postoji put od *v* do *u*. 14 | 15 | Usmjerene grafove možemo rastaviti na disjunktne **strogo povezane komponente** (engl. *strongly connected components*) gdje je svaka komponenta neki podgraf koji je strogo povezan. 16 | 17 | ![Primjer strogo povezanih komponenata u usmjerenom grafu](/img/Scc-1.png) 18 | 19 | Na slici je primjer usmjerenog grafa kojem su označene njegove strogo povezane komponente. One su $\{a, b, e\}$, $\{c, d, h\}$ i $\{f, g\}$. 20 | 21 | ### Algoritam 22 | 23 | Kosarajuev algoritam započinje topološkim sortiranjem vrhova usmjerenog grafa. Nakon toga pronalazimo strogo povezane komponente. 24 | 25 | Potrebno je napisati dva DFS-a. Prvi služi za generiranje topološkog poretka koji spremamo u neku listu. Zatim prvo promijenimo smjer svim bridovima u grafu. Prema prethodno dobivenom topološkom poretku nekom čvoru *x* u njegovu komponentu rekurzivno pridružujemo sve čvorove do kojih postoji put od *x*. Preciznije, u glavnom programu petljom prolazimo po svim čvorovima *u* prema topološkom poretku. Za svaki čvor *u* zovemo `dfs2(u, u)`. Rekurzija `dfs2(x, y)` prvo provjerava je li čvoru *x* već obrađen, odnosno je li mu već pridružena neka komponenta. Ako nije, čvor *x* pridružuje se onoj komponenti u kojoj je već *y*. Nakon toga u rekurziji prolazimo petljom po svim susjedima *v* od *x* te zovemo `dfs2(v, y)`. 26 | 27 | Algoritam je linearne složenosti, odnosno $O(N+M)$. 28 | 29 | ### Implementacija 30 | 31 | ```cpp 32 | #include 33 | #include 34 | #include 35 | #include 36 | using namespace std; 37 | #define N 200200 38 | 39 | int n, m; 40 | int u, v; 41 | vector g[N]; 42 | // ako imamo matricu susjedstva, možemo dobiti inverz i tako da ju transponiramo 43 | vector g_inv[N]; 44 | int bio[N]; 45 | stack topo; 46 | // ovdje zapisujemo rješenje, tako da su dva vrha x i y u istoj komponenti ako je comp[x] == comp[y] 47 | // mogli smo rješenje zapisati i u bio[] samo treba paziti na brojeve koji označavaju komponente i broj koji označava neposjećeni čvor 48 | int comp[N]; 49 | 50 | void dfs1(int cv) 51 | { 52 | if (bio[cv]) return; 53 | bio[cv] = 1; 54 | for (int i: g[cv]) { 55 | dfs1(i); 56 | } 57 | topo.push(cv); 58 | } 59 | 60 | void dfs2(int cv, int root) 61 | { 62 | if (bio[cv]) return; 63 | bio[cv] = 1; 64 | // comp[cv] = comp[root] 65 | comp[cv] = root; 66 | // svim bridovima trebamo zamijeniti smjer pa nas ne zanimaju izlazni nego ulazni bridovi 67 | for (int i: g_inv[cv]) { 68 | dfs2(i, root); 69 | } 70 | } 71 | 72 | int main() 73 | { 74 | scanf("%d%d", &n, &m); 75 | for (int i = 0; i < m; i++) { 76 | scanf("%d%d", &u, &v); 77 | // zapisujemo da čvor u ima izlazni brid prema v 78 | g[u].pb(v); 79 | // zapisujemo da čvor v ima ulazni brid iz u 80 | g_inv[v].pb(u); 81 | } 82 | for (int i = 0; i < n; i++) { 83 | dfs1(i); 84 | } 85 | // možemo reciklirati polje bio[] 86 | memset(bio, 0, sizeof bio); 87 | for (int i = 0; i < n; i++) { 88 | int cv = topo.top(); 89 | topo.pop(); 90 | dfs2(cv, cv); 91 | } 92 | // za svaki čvor ispisujemo kojoj komponenti on pripada 93 | // vrijedi comp[x] == comp[y] ako i samo ako se x i y nalaze u istoj strogo povezanoj komponenti 94 | for (int i = 0; i < n; i++) { 95 | printf("%d\n", comp[i]); 96 | } 97 | return 0; 98 | } 99 | ``` 100 | 101 | ### Odnosi između strogo povezanih komponenata 102 | 103 | Svaki usmjereni graf možemo kondenzirati u usmjereni graf bez ciklusa tako da svaku njegovu strogo povezanu komponentu stegnemo u jedan vrh. 104 | 105 | ![Primjer kondenzacije strogo povezanih komponenata u nekom usmjerenom grafu](/img/kondenzacija.png) 106 | 107 | Na slici je primjer kondenzacije plavog usmjerenog grafa u žuti. Dok plavi ima cikluse, žuti ih nema. Svaka strogo povezana komponenta plavog grafa predstavljena je jednim žutim vrhom. Ako iz nekog vrha u jednoj plavoj komponenti postoji brid prema nekom vrhu u drugoj komponenti, postoji i brid između odgovarajućih žutih vrhova. 108 | 109 | ```cpp 110 | for (int i = 0; i < n; i++) { 111 | for (int j: g[i]) { 112 | if (comp[i] != comp[j]) { 113 | printf("Postoji brid iz cvora %d komponente %d prema cvoru %d komponente %d\n", i, comp[i], j, comp[j]); 114 | } 115 | } 116 | } 117 | ``` -------------------------------------------------------------------------------- /docs/matematika/prosti-brojevi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prosti brojevi 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Je li broj prost? 12 | 13 | Kako odrediti je li neki broj $x$ prost? Možemo jednostavno proći po svim brojevima između $2$ i $x - 1$ te ako $x$ nije djeljiv niti s jednim od tih brojeva onda je prost. Složenost ovog postupka je očito $O(x)$. 14 | 15 | Postoji li brži načina da odredimo prostost broja? Naravno. Primijetimo da nije nužno gledati brojeve veće od $\sqrt x$. Zašto? Neka je $x$ djeljiv s nekim brojem $k$, tada je $x$ nužno djeljiv i s brojem $x/k$. Jedan od brojeva $k$ i $x/k$ nužno je manji ili jednak $\sqrt x$. Razmislite zašto. 16 | 17 | Sada više ne moramo provjeravati $x$, već samo $\sqrt x$ brojeva čime smo složenost spustili na $O(\sqrt x)$. Tu složenost možemo dodatno prepoloviti ako gledamo djeljivost samo s neparnim brojevima, a broj $2$ provjerimo samo jednom, no to često nije nužno. 18 | 19 | ```cpp 20 | //funkcija vraća true ako je broj prost odnosno false ako nije 21 | bool isPrime(int x){ 22 | //1 nije prost 23 | if(x == 1) return false; 24 | for(int i = 2; i <= sqrt(x); i++) if(x % i == 0) return false; 25 | return true; 26 | } 27 | ``` 28 | 29 | ### Eratostenovo sito 30 | 31 | Zanimaju nas svi brojevi manji od $n$ koji su prosti. Naravno možemo primijeniti funkciju napisanu gore i za svaki broj provjeriti je li prost. Složenost tog postupka bila bi $O(n \sqrt n)$. No postoji još jedan način određivanja prostih brojeva koji ima neka korisna svojstva. Riječ je o Eratostenovom situ(engl. sieve of Eratosthenes). Postupak je sljedeći: 32 | 1. Svi brojevi su prosti 33 | 2. Krećemo od 2 i svaki višekratnik tog broja označimo kao **ne** prost 34 | 3. Točku 2 ponavljamo po redu za svaki broj koji je i dalje označen kao prost 35 | 36 | ```cpp 37 | const int MAXN = 1e5 + 5; 38 | bool prosti[MAXN]; 39 | void eratosten(int n){ 40 | //označimo sve višekratnike od 2 kao ne proste 41 | for(int i = 4; i < n; i += 2) 42 | prosti[i] = false; 43 | for(int i = 3; i < n; i += 2){ 44 | if(!prosti[i]) continue; 45 | for(int j = i + i; j < n; j += i) 46 | prosti[i] = false; 47 | } 48 | } 49 | int main(){ 50 | memset(prosti, true, sizeof prosti); 51 | eratosten(MAXN); 52 | //sada u poju prosti imamo označene sve proste brojeve 53 | } 54 | ``` 55 | 56 | Složenost ovog algoritma je $O((\sum_{i=1}^n(n/i))$, što je jednako $O(n * (\sum_{i=1}^n(1/i))$, može se pokazati da je ova suma reda $O(\log n)$, a ukupna složenost ovog algoritma je $O(n (\log n))$, no to ovdje nećemo dokazivati, ono što također možete primijetiti je da će se u sumi nalaziti samo prosti brojevi pa se naša složenost smanjuje na $O(n (\log\log n))$. Možete primijetiti da je član $\log \log n$ jako mali i za potrebe natjecateljskog programiranje ne prelazi $5$ pa ga je gotovo moguće zanemariti, ipak ako je ograničenje gusto ili je *time limit* malen, treba imati na umu da postoji. 57 | 58 | ### Rastav broja na proste faktore 59 | 60 | Eratostenovo sito moguće je prilagoditi kako bi pomoću njega mogli saznati rastav broja na proste faktore. Umjesto da zapisujemo je li broj prost ili ne, u polje ćemo zapisivati koji prosti broj je **poništio** prostost tog broja. 61 | 62 | ```cpp 63 | const int MAXN = 1e5 + 5; 64 | int prosti[MAXN]; 65 | void eratosten(int n){ 66 | for(int i = 4; i < n; i += 2) 67 | prosti[i] = 2; 68 | for(int i = 3; i < n; i += 2){ 69 | if(prosti[i] != 0) continue; 70 | for(int j = i + i; j < n; j += i) 71 | if(prosti[j] == 0) prosti[j] = i; 72 | } 73 | } 74 | void ispisi_rastav(int x){ 75 | while(prosti[x] != 0){ 76 | cout << prosti[x] << ' '; 77 | x = x/prosti[x]; 78 | } 79 | cout << x; 80 | } 81 | int main(){ 82 | memset(prosti, 0, sizeof prosti); 83 | eratosten(MAXN); 84 | int n; 85 | cin >> n; 86 | ispisi_rastav(n); 87 | } 88 | ``` 89 | 90 | Na sljedeći načina dobivamo ispis rastava broja $x$ na proste faktore: 91 | 1. Ispiši broj zapisan na $x$-tom mjestu 92 | 2. Podijeli $x$ s ispisanim brojem 93 | 3. Ako novi broj nije prost vrati se na $1$, inače ispiši taj broj 94 | 95 | Ovim postupkom u složenosti $O(broj\_prostih\_faktora)$ možemo dobiti rastav broja na proste faktore. Broj prostih faktora od $x$ nikad neće biti veći od $\log x$, razmislite zašto, pa će tako složenost biti maksimalno $O(\log x)$. 96 | 97 | **Dokaz**. Uzmimo u obzir sve brojeve koji imaju $n$ prostih faktora, koji je najmanji od njih? To je očito $2^n$, a rastav na proste faktore sastoji se od $n$ dvojki. Za najmanji $x$ koji ima $n$ prostih faktora vrijedi $n <= \log_2(x)$ jer je $n <= log_2(2^n)$, a znamo da vrijedi $\log_2(x) < \log_2(x + 1)$ za sve $x$ pa prema tome $\log_2(x)$ uistinu je gornja granica za broj prostih faktora nekog broja. -------------------------------------------------------------------------------- /docs/algoritmi-nad-grafovima-1/uvod-u-grafove.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Uvod u grafove 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ## Što su grafovi? 12 | 13 | Graf je struktura podataka sastavljena od **vrhova** (čvorova, _eng. node_) i **bridova** (_eng. edge_). Vrhovi opisuju podatke, a bridovi veze između njih. Graf na slici 1 ima $5$ vrhova i $6$ bridova (i izgleda kao Toblerone). 14 | 15 | graph1 16 | 17 | ## Terminologija 18 | 19 | Primijetit ćete da u ovom poglavlju ima _užasno_ puno novih pojmova. Neke koristimo non-stop, a neki se gotovo nikad ne pojavljuju, ali svakako se nemojte opterećivati ako odmah sve na popamtite. Na početku se fokusirajte na dio 'Osnovni pojmovi', a ostalo će doći s vremenom. 20 | 21 | ### Osnovni pojmovi 22 | 23 | - **staza (2A)** = vodi od vrha **a** do vrha **b** (ne ponavljaju se bridovi) 24 | - _put_ je staza u kojoj se ne ponavljaju vrhovi (osim ako je ciklus) 25 | - _duljina staze_ je broj bridova u stazi ili suma težina bridova ako je u pitanju težinski graf 26 | - **ciklus (2B)** = put koji počinje i završava na istom vrhu 27 | - **podgraf (2C)** = (_eng. subgraph_) graf napravljen od podskupa vrhova i bridova nekog originalnog grafa 28 | 29 | ![Terminologija1](/img/algoritmi-nad-grafovima-1/terminologija1.png) 30 | 31 | ### Povezanost grafa 32 | 33 | Graf u kojem postoji put između _svaka_ dva vrha je **povezan**. Na gornjim je slikama graf 2A povezan dok graf 2C nije povezan jer ne postoji put između 0 i 1. Povezani dijelovi grafa zovu se **komponente**. Graf 2C ima dvije komponente: {1, 4, 5} i {0, 2, 3}. 34 | 35 | ### Stablo 36 | 37 | **Stablo (3A)** je povezan graf koji se sastoji od $n$ vrhova i $n-1$ bridova te nema cikluse. Postoji _jedinstven_ put između bilo koja dva vrha stabla. Vrhovi koji imaju samo jednog susjeda zovu se **listovi stabla** (_eng. leaf_). 38 | 39 | Kada prikazujemo stabla često biramo jedan vrh kojeg nazovemo **korijenom** (_eng. root_) od kojeg krećemo crtati stablo. Kažemo da su njegovi prvi susjedi njegova _djeca_, a on je njihov _roditelj_. Preostali susjedi njegove djece su njihova djeca i tako dalje. 40 | 41 | stablo 42 | 43 | ### Bridovi 44 | 45 | - **usmjeren graf (4A)** = (_eng. directed graph_) graf u kojem se preko brida može prijeći samo u jednom smjeru 46 | - **težinski graf (4B)** = (_eng. weighted graph_) graf u kojem je svakom bridu pridružena _težina_; težina se najčešće interpretira kao duljina brida 47 | - duljina puta u težinskom grafu određuje se kao zbroj težina bridova koji čine put 48 | 49 | bridovi 50 | 51 | ### Susjedi 52 | 53 | Za dva vrha kažemo da su **susjedni** (_eng. neighbours ili adjacent_) ako između njih postoji brid. **Stupanj** (_eng. degree_) nekog vrha je broj njegovih susjeda. Na grafu 4B vrh 2 ima susjede 0 i 3 pa je njegov stupanj 2. 54 | 55 | Graf je **regularan** (_eng. regular_) ako su svi vrhovi istog konstantnog stupnja $d$, a **potpun** (_eng. complete_) ako je stupanj svakog vrha $n-1$ (gdje je $n$ ukupan broj vrhova). U potpunom grafu postoji brid između svaka dva vrha. 56 | 57 | Vrhovi usmjerenog grafa imaju ulazni i izlazni stupanj. **Ulazni stupanj** (_eng. indegree_) vrha je broj bridova koji završavaju u tom vrhu, a **izlazni stupanj** (_eng. outdegree_) je broj bridova koji počinju u tom vrhu. 58 | 59 | ### Bojanje grafova 60 | 61 | Pri bojanju grafa (_eng. colouring_) svakom je vrhu dodijeljena boja tako da ne postoje dva susjedna vrha iste boje. Graf je $k$-obojiv ako ga možemo obojiti koristeći $k$ boja, a specijalno ako je $k=2$ kažemo da je graf **bipartitni (5)** (_eng. bipartite graph_). **Kromatski broj** grafa je najmanji $k$ za koji je graf $k$-obojiv. 62 | 63 | bipartitni_graf 64 | 65 | ## Zašto učiti grafove? 66 | 67 | Puno se problema u programiranju može prikazati pomoću grafova. Tipičan primjer takvog problema je mreža cesta i gradova. Ceste prikazujemo kao bridove, a gradove kao vrhove. Sada se možemo pitati postoji li put između neka dva grada ili, ako znamo da postoji više puteva, kolika je duljina najkraćeg puta. Koji gradovi čine povezanu cjelinu unutar koje postoji put između svih gradova? Ulice unutar gradova možemo prikazati kao usmjereni težinski graf gdje je težina brida duljina ulice, a smjer određuje je li ulica dvosmjerna ili jednosmjerna. 68 | 69 | Ipak, nisu svi problemi s grafovima tako očiti. Ponekad ćemo grafom prikazivati odnose zaposlenika u nekoj tvrtki ili obiteljsko stablo. Više-manje svi problemi koji se mogu riješiti dinamičkim programiranjem mogu se gledati kao problemi s grafovima. 70 | 71 | U svakom slučaju, ne želite preskočiti grafove! 😄 72 | -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/pohlepni-pristupi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pohlepni pristupi" 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | **Pohlepni pristupi** predstavljaju skup algoritamskih postupaka u kojima se potpuno rješenje problema 10 | komponira poduzimajući korake koji se u djelomičnom rješenju čine *lokalno najboljima*. 11 | 12 | ### Problem 3: Problem razmjene novca (coin change problem) 13 | 14 | Zadana je cjelobrojna vrijednost $x$, koja predstavlja ukupnu količinu novca koju treba razmijeniti. 15 | U sljedećem retku dan je broj $k$, broj različitih novčanica koje su nam dostupne. 16 | U trećem retku dano je $k$ brojeva, $a_1, a_2, ..., a_n$ - novčanice koje su nam dostupne. 17 | 18 | Svake novčanice dostupno je beskonačno mnogo. Barem jedna od novčanica ima vrijednost $1$, 19 | tako da je moguće razmijeniti bilo koji iznos. 20 | 21 | Cilj je razmijeniti iznos koristeći najmanji broj novčanica. Novčanice koje uzmemo više puta 22 | broje se toliko puta. 23 | 24 | #### Pohlepni pristup 25 | 26 | Neka je skup novčanica jednak $\{1,2,5,10\}$. 27 | Pohlepni pristup koji nama pada napamet pretpostavlja da uzmemo najveću novčanicu koju imamo, 28 | sve što možemo vratimo pomoću nje, a zatim krenemo na manje. 29 | 30 | Za različite količine $x$ dobili bi sljedeće postupke: 31 | 32 | | $X$ | Postupak | Količina novčanica | 33 | | ------------- | :-----------: | -----: | 34 | | 8 | 5,2,1 | 3 | 35 | | 17 | 10,5,2 | 3 | 36 | | 11 | 10,1 | 2 | 37 | 38 | Za ovaj početni skup novčanica (kao i za hrvatski sustav novčanica) vrijedi da 39 | pohlepni pristup daje **optimalno rješenje**. Prije nego krenemo na dokaz, 40 | pogledajmo rješenja koja daje ovaj pohlepni pristup za skup $\{1,3,4,5\}$: 41 | 42 | | $X$ | Postupak | Količina novčanica | 43 | | ------------- | :-----------: | -----: | 44 | | 8 | 5,3 | 2 | 45 | | 7 | 5,1,1 | 3 | 46 | 47 | Pozorni čitatelj primjetit će da slučaj sa $x=7$ možemo razložiti bolje nego što 48 | je to učinio pohlepni pristup - $7 = 4 + 3$. Dakle, pohlepni pristup nije optimalan 49 | za sve skupove novčanica. 50 | 51 | Zbog ovog neočekivanog problema bitno je naučiti 52 | dokazivati optimalnost različitih pohlepnih pristupa. 53 | Iako na natjecanjima često preskačemo formalne dokaze, 54 | gotovo uvijek neke od njihovih dijelova intuitivno shvatimo prilikom rješavanja. 55 | Ovim dokazima pokušat ćemo čitatelju približiti taj proces zaključivanja. 56 | 57 | #### Optimalnost pohlepnog pristupa nakon određenog iznosa 58 | 59 | Intuitivno, jasno je da kad je $x$ *ogroman* (u odnosu na najveću novčanicu $M$), 60 | tu novčanicu trebamo iskorištavati mnogo puta (dok se iznos ne smanji). 61 | Pokušajmo odrediti što točno znači *ogroman*. 62 | 63 | Neka je $M$ najveća novčanica iz skupa $S$. 64 | Dokazat ćemo da (**ovaj**, od sad implicitno) pohlepni algoritam 65 | daje **optimalno rješenje** za sve $x$, pod uvjetom 66 | da daje **optimalno rješenje** za sve $x \leq 2 \cdot M - 1$. 67 | Primijetimo da je poredak razmjene novčanica nebitan, 68 | pa se možemo fokusirati na one poretke u kojima prvo vraćamo veće novčanice. 69 | 70 | 1. **Baza indukcije**: 71 | - pohlepni algoritam daje optimalno rješenje za sve $x \leq 2 \cdot M -1$. 72 | 73 | 2. **Korak indukcije**: 74 | * dokažimo da optimalnost pohlepnog algoritma za sve $x \leq n$ povlači optimalnost algoritma 75 | za sve $x \leq n+1$. Za $x \leq 2 \cdot M-1$ tvrdnja vrijedi iz pretpostavke. 76 | Za $x = n+1$ pretpostavimo suprotno, tj. da pohlepni algoritam nije optimalan. 77 | Imamo dva slučaja: 78 | * a) optimalno rješenje u sebi ima novčanicu $M$. Ako je to istina, onda smo taj $M$ mogli uzeti 79 | odmah (što bi napravio i pohlepni algoritam), a onda bi nam optimalnost za $x \leq n$ tvrdila 80 | da i dalje slijedimo pohlepni algoritam. U ovom slučaju je pohlepni algoritam optimalan - kontradikcija. 81 | * b) optimalno rješenje u sebi nema novčanicu $M$. Ako je to istina, onda će se zamjena eventualno svesti na vraćanje $y$ novca, $M \leq y \leq 2 \cdot M -1$ (jer ne možemo preskočiti taj raspon veličine $M$). 82 | Budući da je u tom intervalu pohlepni algoritam optimalan, on će sigurno uzeti i novčanicu $M$ (po definiciji pohlepnog algoritma) - kontadikcija s tim da takve novčanice u rješenju uopće nema. 83 | * U svakom slučaju dobivamo kontradikciju - pohlepni algoritam ipak jest optimalan. 84 | 85 | Induktivnim zaključivanjem dokazali smo da optimalnost pohlepnog algoritma za 86 | sve vrijednost $x \leq 2 \cdot M - 1$ implicira globalnu optimalnost pohlepnog algoritma. 87 | 88 | Drugim riječima, dovoljno je provjeriti je li pohlepni algoritam ispravan 89 | samo za konačno mnogo vrijedosti $x$. Ipak, kako ovo provjeriti naučit ćemo 90 | tek na predavanju vezanom za *dinamičko programiranje*. 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docusaurus.config.js: -------------------------------------------------------------------------------- 1 | const math = require("remark-math"); 2 | const katex = require("rehype-katex"); 3 | 4 | module.exports = { 5 | title: "NatPro", 6 | tagline: "Materijali za natjecateljsko programiranje", 7 | url: "https://materijali.xfer.hr", 8 | baseUrl: "/", 9 | onBrokenLinks: "throw", 10 | onBrokenMarkdownLinks: "warn", 11 | favicon: "img/favicon.ico", 12 | organizationName: "x-fer", // Usually your GitHub org/user name. 13 | projectName: "natpro", // Usually your repo name. 14 | themeConfig: { 15 | navbar: { 16 | title: "NatPro", 17 | logo: { 18 | alt: "My Site Logo", 19 | src: "img/XFER-Logo.svg", 20 | }, 21 | items: [ 22 | { 23 | to: "docs/o-materijalima/sadrzaj", 24 | activeBasePath: "docs", 25 | activeBaseRegex: "docs/((?!doprinos-ovim-materijalima).)*$", 26 | label: "Materijali", 27 | position: "left", 28 | }, 29 | { 30 | to: "blog", 31 | label: "Blog", 32 | position: "left", 33 | }, 34 | { 35 | to: "docs/doprinos-ovim-materijalima/kako-napisati-clanak", 36 | activeBasePath: "docs/doprinos-ovim-materijalima", 37 | label: "Doprinesite!", 38 | position: "left", 39 | }, 40 | { 41 | href: "https://xfer.hr", 42 | label: "X.FER", 43 | position: "right", 44 | }, 45 | { 46 | href: "https://github.com/x-fer/natpro", 47 | label: "GitHub", 48 | position: "right", 49 | }, 50 | ], 51 | }, 52 | footer: { 53 | style: "dark", 54 | links: [ 55 | { 56 | title: "Sadržaj", 57 | items: [ 58 | { 59 | label: "Materijali", 60 | to: "docs/o-materijalima/sadrzaj", 61 | }, 62 | { 63 | label: "Blog", 64 | to: "blog", 65 | }, 66 | { 67 | label: "Doprinesite", 68 | to: "docs/doprinos-ovim-materijalima/kako-napisati-clanak", 69 | }, 70 | ], 71 | }, 72 | { 73 | title: "Društvene mreže", 74 | items: [ 75 | { 76 | label: "LinkedIn", 77 | href: "https://linkedin.com/company/xferhr", 78 | }, 79 | { 80 | label: "Facebook", 81 | href: "https://facebook.com/xferhr", 82 | }, 83 | { 84 | label: "Instagram", 85 | href: "https://instagram.com/xfer_hr", 86 | }, 87 | ], 88 | }, 89 | { 90 | title: "Još stvari", 91 | items: [ 92 | { 93 | label: "X.FER", 94 | href: "https://xfer.hr", 95 | }, 96 | { 97 | label: "GitHub", 98 | href: "https://github.com/x-fer/natpro", 99 | }, 100 | { 101 | label: "Discord", 102 | href: "https://discord.gg/E7ad4UGbrG", 103 | }, 104 | ], 105 | }, 106 | ], 107 | copyright: `Copyright © ${new Date().getFullYear()} X.FER`, 108 | }, 109 | colorMode: { 110 | defaultMode: "dark", 111 | respectPrefersColorScheme: false, 112 | }, 113 | announcementBar: { 114 | id: "support_us", 115 | content: 116 | '⭐️ Podržite nas davanjem zvjezdice na GitHubu! ⭐️', 117 | backgroundColor: "#FF5F6D", 118 | textColor: "#fff", 119 | }, 120 | prism: { 121 | defaultLanguage: "cpp", 122 | }, 123 | }, 124 | stylesheets: [ 125 | { 126 | href: "https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css", 127 | type: "text/css", 128 | integrity: 129 | "sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X", 130 | crossorigin: "anonymous", 131 | }, 132 | ], 133 | presets: [ 134 | [ 135 | "@docusaurus/preset-classic", 136 | { 137 | docs: { 138 | sidebarPath: require.resolve("./sidebars.js"), 139 | // Please change this to your repo. 140 | editUrl: "https://github.com/x-fer/natpro/edit/main/", 141 | remarkPlugins: [math], 142 | rehypePlugins: [katex], 143 | showLastUpdateTime: false, 144 | showLastUpdateAuthor: false, 145 | }, 146 | blog: { 147 | showReadingTime: true, 148 | // Please change this to your repo. 149 | editUrl: "https://github.com/x-fer/natpro/edit/main/", 150 | }, 151 | theme: { 152 | customCss: require.resolve("./src/css/custom.css"), 153 | }, 154 | }, 155 | ], 156 | ], 157 | i18n: { 158 | defaultLocale: "hr", 159 | locales: ["hr"], 160 | localeConfigs: { 161 | hr: { 162 | label: "Hrvatski", 163 | direction: "ltr", 164 | }, 165 | }, 166 | }, 167 | staticDirectories: ["static"], 168 | }; 169 | -------------------------------------------------------------------------------- /docs/dinamicko-programiranje/najdulji-rastuci-podniz.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Najdulji rastući podniz 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Problem 12 | Jedan od klasičnih problema dinamičkog programiranja je određivanje najduljeg rastućeg podniza zadanog niza. 13 | 14 | Zadan je niz duljine $N$ čiji su elementi $x_i < 1e5$. Odredi najdulji podniz, ne nužno uzastopnih elemenata, za čije elemente vrijedi da je svaki element veći ili jednak od elemenata prije njega. 15 | 16 | ### Rješenje 17 | 18 | Za ovaj problem postoji jednostavno rješenje. Neka nam je stanje duljina najduljeg podniza razmatrajući niz do nekog elementa(uključujući taj element), a funkcija prijelaza je prolazak po svim prethodnim elementima gdje od svih elemenata koji su manji ili jednaki trenutnom tražimo onaj koji do tada tvori najdulji podniz, odnosno onaj u čijoj je dinamici zapisan najveći broj. Tom broju dodajemo 1 te to postaje vrijednost trenutnog stanja dinamike. 19 | 20 | ```cpp 21 | int n, sol = 0; 22 | int niz[100005]; 23 | int dp[100005]; 24 | 25 | int main(){ 26 | memset(dp, 1, sizeof dp); 27 | cin >> n; 28 | for(int i = 0; i < n; i++){ 29 | cin >> niz[i]; 30 | for(int j = 0; j < i; j++){ 31 | if(niz[j] <= niz[i]) 32 | dp[i] = max(dp[i], dp[j] + 1); 33 | } 34 | sol = max(sol, dp[i]); 35 | } 36 | cout << sol; 37 | return 0; 38 | } 39 | ``` 40 | 41 | ### Analiza složenosti 42 | 43 | Postoji $N$ stanja, a za izračun stanja moramo provjeriti sva prijašnja stanja, stoga je složenost prijelaza $O(N)$. Ukupna složenost je dakle $O(N^2)$. Nažalost ovo rješenje često nije dovoljno jer su ograničenja uglavnom $N < 1e5$. Stoga nam treba neko brže rješenje. 44 | 45 | ### Brže rješenje 46 | 47 | Uvedimo novi niz koji će na mjestu $x$ imati zapisan najmanji broj koji može biti na kraju podniza duljine $x$. Zašto nam je taj niz koristan? Upravo njegova duljina odgovara na naš problem. Sada se postavlja pitanje možemo li taj niz stvoriti nešto brže kako bi nam rješenje stalo u vremensko ograničenje. 48 | 49 | ```cpp 50 | int n, x; 51 | vector najmanji; 52 | 53 | int main(){ 54 | cin >> n; 55 | najmanji.push_back(-1e9); 56 | for(int i = 0; i < n; i++){ 57 | cin >> x; 58 | //ako je zadnji broj u podnizu manji od novog broja, tada možemo novi broj staviti kao zadnji broj u podnizu 59 | //time smo dodali novi zadnji broj i popravili trenutno rješenje 60 | if(najmanji.back() <= x) najmanji.push_back(x); 61 | for(int j = najmanji.size() - 2; j > 0; j--) 62 | //ako je uneseni broj veći od trenutnog onda se može nalaziti iza njega u potencijalnom rješenju 63 | //taj ćemo broj zapisati na sljedeće mjesto samo ako je manji od broja koji je tamo već zapisan kako bi popravili rješenje 64 | //time smo popravili podniz duljine j + 1 tako da sad završava s još manjim brojem 65 | if(najmanji[j] <= x && x < najmanji[j + 1]) 66 | najmanji[j + 1] = x; 67 | } 68 | cout << najmanji.size() - 1; 69 | return 0; 70 | } 71 | ``` 72 | 73 | Pogledajmo složenost ovog rješenja. Vidimo da za svaki uneseni broj prolazimo kroz cijeli podniz kako bi ga popravili. Što ako nam je ulazni niz već sortiran ulazno? Tada će polje $najmanji$ stalno rasti i to nam je najgori slučaj koji i dalje daje kvadratnu složenost. Možemo li kako riješiti taj problem? 74 | 75 | 76 | Bitno je primijetiti da će brojevi u tom nizu uvijek biti sortirani uzlazno. Razmislite zašto. Također možemo primijetiti da ćemo popravak podniza napraviti samo jednom u prolasku kroz cijeli podniz. Promjena jednog elementa u sortiranom nizu navodi nas na binarnu pretragu. Binarnom pretragom možemo naći mjesto na kojem trebamo napraviti promjenu i time ne moramo prolaziti kroz čitav podniz. Složenost tog rješenja bit će $O(rješenje \log rješenje)$, odnosno u najgorem slučaju $O(n \log n)$. 77 | 78 | ```cpp 79 | int n, x; 80 | vector najmanji; 81 | vector::iterator it; 82 | 83 | int main(){ 84 | cin >> n; 85 | najmanji.push_back(-1e9); 86 | for(int i = 0; i < n; i++){ 87 | cin >> x; 88 | it = upper_bound(najmanji.begin(), najmanji.end(), x); 89 | if(it == najmanji.end()) najmanji.push_back(x); 90 | else *it = x; 91 | } 92 | cout << najmanji.size() - 1; 93 | return 0; 94 | } 95 | ``` 96 | 97 | Ovaj zadatak savršen je primjer zadatka dinamičkog programiranja. Jako kratak kod koji je teško smisliti. 98 | 99 | ### Najdulji padajući podniz 100 | 101 | Na prvi pogled potrebno je promijeniti dinamiku i u vektor spremati najveće brojeve, no stvari se mogu zakomplicirati jer je vektor sada silazno sortiran pa više ne smijemo koristiti $upper\_bound()$ nego bismo trebali kodirati vlastitu binarnu pretragu. Na svu sreću problem možemo riješiti vrlo jednostavno tako da spremimo ulazni niz u neko polje i zatim primijenimo funkciju $reverse()$ na to polje. Tada problem postaje traženje najduljeg **rastućeg** podniza. 102 | -------------------------------------------------------------------------------- /docs/matematika/osnove-geometrije.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Osnove geometrije 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Implementacija točke 12 | 13 | Točku u 2D možemo implementirati kao strukturu koja pamti koordinate, tako da _tip_podatka_ zamijenimo odgovarajućim tipom, najčešće _int_, _long long_ ili _double_. Svaki isječak koda koji slijedi možemo prilagoditi i za 3D slučaj. 14 | ```cpp 15 | struct tocka { 16 | tip_podatka x, y; 17 | }; 18 | ``` 19 | Možemo je doživljavati kao uređeni par koordinata, ali i kao vektor s početkom u ishodištu i krajem u $(x,y)$, pa pojmove vektora i točke možemo poistovjećivati. Sljedeći operatori, na primjer, baziraju se na vizualizaciji točke kao vektora: 20 | ```cpp 21 | struct tocka { 22 | tip_podatka x, y; 23 | 24 | tocka& operator+=(const tocka &t) { 25 | x += t.x; 26 | y += t.y; 27 | return *this; 28 | } 29 | tocka operator+(const tocka &t) const { 30 | tocka rez = *this; 31 | rez += t; 32 | return rez; 33 | } 34 | tocka& operator*=(tip_podatka t) { 35 | x *= t; 36 | y *= t; 37 | return *this; 38 | } 39 | tocka operator*(tip_podatka t) const { 40 | tocka rez = *this; 41 | rez *= t; 42 | return rez; 43 | } 44 | /* slično za oduzimanje i dijeljenje */ 45 | }; 46 | tocka operator*(tip_podatka a, tocka b) { 47 | return b * a; 48 | } 49 | ``` 50 | ### Skalarni produkt 51 | 52 | Skalarni produkt dvaju vektora može se definirati na dva ekvivalentna načina. 53 | Geometrijski, on je duljina projekcije jednog vektora na drugi, pa iznosi $\overrightarrow{v_1} \cdot \overrightarrow{v_2} =|\overrightarrow{v_1}| |\overrightarrow{v_2}| \cos{\theta}$, gdje je $\theta$ mjera kuta između njih. 54 | Algebarski, on je zbroj umnožaka odgovarajućih koordinata. Uz zapis $\overrightarrow{v_1} = (x_1, y_1)$ i $\overrightarrow{v_2} = (x_2, y_2)$, iznosi $\overrightarrow{v_1} \cdot \overrightarrow{v_2} = x_1 x_2 + y_1 y_2$ (s dodatnom z-koordinatom u 3D slučaju). 55 | 56 | ```cpp 57 | tip_podatka skalarni_produkt(tocka a, tocka b) { 58 | return a.x * b.x + a.y * b.y; 59 | } 60 | ``` 61 | 62 | ### Vektorski produkt 63 | 64 | Vektorski produkt definiramo samo za 3D vektore, i također ima više interpretacija. Geometrijski, vektorski produkt vektora $\overrightarrow{v_1}$ i $\overrightarrow{v_2}$ novi je vektor koji je po smjeru okomit na oba faktora, a po iznosu površina paralelograma kojeg $\overrightarrow{v_1}$ i $\overrightarrow{v_2}$ razapinju ($|\overrightarrow{v_1}| |\overrightarrow{v_2}| \sin{\theta}$). Algebarski se može dobiti kao sljedeća determinanta: 65 | 66 | $\overrightarrow{v_1} \times \overrightarrow{v_2} = \begin{vmatrix} \overrightarrow{e_1} & \overrightarrow{e_2} & \overrightarrow{e_3} \\ x_1 & y_1 & z_1 \\ x_2 & y_2 & z_2 \end{vmatrix}$ 67 | 68 | gdje je $\overrightarrow{e_1} = (1,0,0)$, $\overrightarrow{e_2} = (0,1,0)$ a $\overrightarrow{e_3} = (0,0,1)$. Raspisan izgleda ovako: 69 | 70 | $\overrightarrow{v_1} \times \overrightarrow{v_2} = (y_1 z_2 -z_1 y_2) \overrightarrow{e_1} +(z_1 x_2 − x_1 z_2) \overrightarrow{e_2} + (x_1 y_2 − y_1 x_2) \overrightarrow{e_3}$. 71 | 72 | ```cpp 73 | struct tocka { 74 | tip_podatka x, y, z; 75 | } 76 | tocka vektorski_produkt(tocka a, tocka b) { 77 | tocka rez; 78 | rez.x = a.y * b.z - a.z * b.y; 79 | rez.y = a.z * b.x - a.x * b.z; 80 | rez.z = a.x * b.y - a.y * b.x; 81 | return rez; 82 | } 83 | ``` 84 | Zanimljiva primjena vektorskog produkta je u računanju volumena paralelepipeda. Ako su njegove stranice vektori $\overrightarrow{a}$, $\overrightarrow{b}$ i $\overrightarrow{c}$, volumen možemo dobiti kao $|\overrightarrow{a} \cdot \left( \overrightarrow{b} \times \overrightarrow{c} \right) |$. 85 | 86 | #### Pseudoskalarni produkt 87 | 88 | Nešto slično vektorskom produktu možemo implementirati i u 2D ravnini: površinu paralelograma kojeg razapinju $\overrightarrow{a}$ i $\overrightarrow{b}$ možemo dobiti kao $|\overrightarrow{e_3} \cdot \left( \overrightarrow{a} \times \overrightarrow{b} \right) | = |x_1 y_2−y_1 x_2|$. Tako i definiramo pseudoskalarni produkt: 89 | 90 | ```cpp 91 | tip_podatka pseudoskalarni(tocka a, tocka b) { 92 | return a.x * b.y - a.y * b.x; 93 | } 94 | ``` 95 | 96 | #### Orijentacija kuta 97 | 98 | Bitna primjena ovog produkta je u računanju orijentacije kuta između dvaju vektora. Može se pokazati da, ako kut između $\overrightarrow{a}$ i $\overrightarrow{b}$ opisujemo u smjeru kazaljke na satu (_clockwise_), njihov pseudoskalarni produkt bit će negativan, a inače (u _counterclockwise_ smjeru) pozitivan. Kut može biti zadan i točkama; ako su dane tri točke koje određuju kut ($P_1$ na jednom kraku, vrh $P_2$, $P_3$ na drugom kraku), orijentaciju kuta možemo provjeriti sljedećim funkcijama: 99 | ```cpp 100 | bool clockwise(tocka p1, tocka p2, tocka p3) { 101 | return pseudoskalarni(p1 - p2, p3 - p2) < 0; 102 | } 103 | bool counterclockwise(tocka p1, tocka p2, tocka p3) { 104 | return pseudoskalarni(p1 - p2, p3 - p2) > 0; 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/dinamicko-programiranje/tiling.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Popločavanje 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Problem 12 | 13 | Zadana je ploča veličine $2 * n$ koju je potrebno popločiti pločicama dimenzija $2 * 1$. Pločice je moguće rotirati. Na koliko načina je moguće ostvariti popločavanje? Pošto broj načina može biti jako velik, ispišite rješenje modulo 1e9 + 7. 14 | 15 | ### Rješenje 16 | 17 | Problem ćemo riješiti rekurzivno. Ako je ploča duljine $1$, tada možemo postaviti pločicu samo vertikalno, inače imamo dvije opcije. Možemo staviti pločicu vertikalno i time smanjiti duljinu ploče za $1$ **ili** staviti pločicu horizontalno čime smo prisiljeni staviti i drugu pločicu horizontalno u iste stupce, time smanjujemo duljinu ploče za $2$. Tada se naša relacija svodi na sljedeće $F(x) = F(x - 1) + F(x - 2)$ što prepoznajemo kao *Fibonaccijev niz*. 18 | 19 | ```cpp 20 | const int MAXN = 1e5 + 5; 21 | int n; 22 | int mod = 1e9 + 7; 23 | int memo[MAXN]; 24 | 25 | int solve(int ostalo){ 26 | if(ostalo == 0) return 0; 27 | if(ostalo == 1) return 1; 28 | if(memo[ostalo] != -1) return memo[ostalo]; 29 | return memo[ostalo] = (solve(ostalo - 1) + solve(ostalo - 2)) % mod; 30 | } 31 | 32 | int main(){ 33 | memset(memo, -1, sizeof memo); 34 | cin >> n; 35 | cout << solve(n); 36 | return 0; 37 | } 38 | ``` 39 | Složenost ovog rješenja je $O(n)$ jer svako stanje posjećujemo samo jednom. 40 | 41 | ### Teži problem 42 | 43 | Zadana je ploča veličine $2 * n$ koju je potrebno popločiti pločicama dimenzija $2 * 1$. Pločice je moguće rotirati. *Neka polja su blokirana i preko njih nije moguće postaviti pločicu.* Na koliko načina je moguće ostvariti popločavanje? Pošto broj načina može biti jako velik, ispišite rješenje modulo $1e9 + 7$. 44 | 45 | Ovaj jedan dodatni uvjet znatno nam otežava rješavanje zadatka jer prethodna formula više ne pomaže. 46 | 47 | ### Rješenje 48 | 49 | Neka nam je stanje određeno trenutnim stupcem i vrstom bloka u tom stupca, taj stupac još nije riješen, ali neka su svi stupci prije njega riješeni. Postoje 4 vrste bloka: 50 | 51 | 52 | 53 | 0 - u stupcu nema bloka 54 | 55 | 56 | 1 - postoji blok u gornjem retku 57 | 58 | 59 | 2 - postoji blok u donjem retku 60 | 61 | 62 | 3 - postoji blok u oba retka 63 | 64 | 65 | 66 | 67 | Funkciju prijelaza više nije trivijalno odrediti, nego za svaku vrstu bloka moramo odrediti posebnu funkciju prijelaza. Funkcije prijelaza možete pogledati u kodu. 68 | 69 | ```cpp 70 | const int MAXN = 1e5 + 5; 71 | int n; 72 | int mod = 1e9 + 7; 73 | int dp[MAXN][4]; 74 | int blok[MAXN]; 75 | string prvi, drugi; 76 | 77 | int main(){ 78 | //prvi i drugi predstavljaju retke ploče na kojoj '.' predstavlja prazno, a 'X' blokirano polje 79 | cin >> n >> prvi >> drugi; 80 | for(int i = 0; i < n; i++){ 81 | if(prvi[i] == '.' && drugi[i] == '.') blok[i] = 0; 82 | if(prvi[i] != '.' && drugi[i] == '.') blok[i] = 1; 83 | if(prvi[i] == '.' && drugi[i] != '.') blok[i] = 2; 84 | if(prvi[i] != '.' && drugi[i] != '.') blok[i] = 3; 85 | } 86 | dp[0][0] = (blok[0] == 0); 87 | dp[0][1] = (blok[0] == 1); 88 | dp[0][2] = (blok[0] == 2); 89 | // | predstavlja bitovno ili 90 | dp[0][3] = (blok[0] == 3) | (blok[0] == 0); 91 | for(int i = 1; i < n; i++){ 92 | if(blok[i] == 0){ 93 | dp[i][0] = dp[i - 1][3]; 94 | dp[i][1] = dp[i - 1][2]; 95 | dp[i][2] = dp[i - 1][1]; 96 | dp[i][3] = (dp[i - 1][3] + dp[i - 1][0]) % mod; 97 | } 98 | if(blok[i] == 1){ 99 | dp[i][1] = dp[i - 1][3]; 100 | dp[i][3] = dp[i - 1][1]; 101 | } 102 | if(blok[i] == 2){ 103 | dp[i][2] = dp[i - 1][3]; 104 | dp[i][3] = dp[i - 1][2]; 105 | } 106 | if(blok[i] == 3){ 107 | dp[i][3] = dp[i - 1][3]; 108 | } 109 | } 110 | cout << dp[n - 1][3] << endl; 111 | return 0; 112 | } 113 | 114 | ``` 115 | 116 | ### Analiza složenosti 117 | 118 | Dinamika ima $4 * n$ stanja, a složenost prijelaza je $O(1)$, tako da je ukupna složenost dinamike $O(n)$. 119 | 120 | ### Još teži problem 121 | 122 | Problem možemo dodatno otežati tako da, umjesto ploče, polja koja su blokirana unosimo njihovim koordinatama. Neka je $m < 1e5$ blokiranih polja, tada je moguće dopustiti $n < 1e9$. Rješenje ovog problema nećemo prikazati ovdje te ga ostavljamo čitatelju za vježbu. 123 | 124 |
125 | HINT 126 |

127 | Blokove je potrebno sortirati po x koordinati te ih potom upariti. Ako se blokovi u paru nalaze u istom stupcu onda taj stupac dijeli problem na dva manja problema(lijevo i desno od stupca). Inače je riješenje između dva bloka u paru determinirano ili ne postoji pa je konačno rješenje 0. 128 |

129 |
130 |
131 | HINT 2 132 |

133 | Zadatak se svodi na određivanje rješenja za velik broj ne blokiranih ploča, a konačno rješenje je umnožak svih rješenja. Pošto znamo odrediti rješenje jedne ploče pomoću Fibonaccijevog niza, a element Fibonaccijevog niza je moguće izračunati u složenosti O(log n) tada je i ukupno rješenje složenosti O(m * log(n)) jer ćemo imati maksimalno m / 2 + 1 manju ploču. 134 |

135 |
-------------------------------------------------------------------------------- /docs/dinamicko-programiranje/problem-razmjene-novca.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Problem razmjene novca 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Riječ je o problemu s kojim smo se susreli u poglavlju o pohlepnim pristupima. Pogledajmo kako bismo taj problem riješili dinamičkim programiranjem. 12 | 13 | ### Rješenje 14 | 15 | Rješenje ovog problema blago se razlikuje u iterativnom i rekurzivnom pristupu. Prvo ćemo problem riješiti iterativno, a zatim rekurzivno. 16 | Neka je stanje neka količina novca koju smo do sad razmijenili. Stanje $DP[i]$ označava najmanji broj novčanica s kojima možemo razmijeniti količinu novca $i$. Iz tog stanja s jednom novčanicom možemo prijeći u stanja koja su veća upravo za vrijednost neke novčanice koja nam je na raspolaganju. Krenimo od početnog stanja, u ovom zadatku zapravo imamo $k$ početnih stanja jer svaku količinu novca koja je jednaka jednoj od novčanica možemo razmijeniti upravo s tom jednom novčanicom te je vrijednost tih stanja $1$, sva ostala stanja imaju vrijednost $beskonačno$. Neka je trenutno stanje $i$, za svako stanje radimo sljedeće: povećamo $i$ za vrijednost svake novčanice i pogledamo jesmo li popravili rješenje koje smo već imali za tu(novu) vrijednost. Naravno uvijek uzimamo minimum od prethodnog i novog rješenja kako bismo dobili optimalnu razmjenu. 17 | 18 | ***Ograničenja*** 19 | 20 | $x < 10000$ 21 | 22 | $k < 100$ 23 | 24 | $a_{i} < 10000$ 25 | 26 | ***ITERATIVNO*** 27 | ```cpp 28 | //pazimo na ovu veličinu kako tokom rješavanje ne bi izašli iz nekog polja 29 | const int MAXN = 20000; 30 | 31 | int x, k 32 | int novcanice[MAXN]; 33 | int sol[MAXN]; 34 | 35 | int main(){ 36 | //pretpostavimo da nam za razmjenu bilo koje količine novca treba beskonačno novčanica 37 | memset(sol, 1e9, sizeof(sol)); 38 | cin >> k >> x; 39 | for(int i = 0; i < k; i++){ 40 | cin >> novcanice[i]; 41 | //svaku količinu koja je jednaka nekoj novčanici možemo razmijeniti upravo s tom (jednom) novčanicom 42 | sol[novcanice[i]] = 1; 43 | } 44 | for(int i = 1; i <= x; i++) 45 | for(int j = 0; j < k; j++) 46 | //funkcija prijelaza 47 | sol[i + novcanice[j]] = min(sol[i + novcanice[j]], sol[i] + 1); 48 | //Na x-tom mjestu se nalazi traženo rješenje 49 | cout << sol[x]; 50 | return 0; 51 | } 52 | ``` 53 | 54 | ***REKURZIVNO*** 55 | ```cpp 56 | const int MAXN = 20000; 57 | 58 | int x, k 59 | int novcanice[MAXN]; 60 | int memo[MAXN]; 61 | 62 | int solve(int ostatak){ 63 | //ako je ostatak manji od 0 znaci da nismo na dobar nacin razmijenili novce 64 | if(ostatak < 0) return 1e9; 65 | //ako je ostatak jednak 0 znaci da smo sve novce razmijenili sa novcanicama koje su nam na raspolaganju 66 | if(ostatak == 0) return 0; 67 | 68 | //ako smo vec racunali kako najbolje razmijeniti ostatak onda to ne moramo ponovno racunati 69 | if(memo[ostatak] != -1) return memo[ostatak]; 70 | 71 | int ret = 1e9; 72 | 73 | for(int i = 0; i < k; i++) 74 | ret = min(ret, 1 + solve(ostatak - novcanice[i])); 75 | return memo[ostatak] = ret; 76 | } 77 | 78 | int main(){ 79 | //pretpostavimo da nam za razmjenu bilo koje količine novca treba beskonačno novčanica 80 | memset(memo, -1, sizeof(sol)); 81 | cin >> k >> x; 82 | for(int i = 0; i < k; i++) cin >> novcanice[i]; 83 | cout << solve(x); 84 | return 0; 85 | } 86 | ``` 87 | 88 | ### Analiza složenosti 89 | 90 | Prisjetimo se kako se određuje složenost dinamike. Složenost dinamike jednaka je umnošku broja stanja i složenosti prijelaza. Naše rješenje ima stanja koliko novaca želimo razmijeniti, odnosno $x$. Iz svakog stanja imamo $k$ prijelaza, a složenost svakog prijelaza je $O(1)$, stoga je složenost prijelaza $O(k)$. Time je ukupna složenost ovog rješenja $O(x * k)$ 91 | 92 | ### Poveznica s pohlepnim pristupom 93 | 94 | Ovom dinamikom možemo provjeriti bi li nam pohlepni algoritam objašnjen u predavanju o *Potpunom pretraživanju i pohlepnim pristupima* dao optimalno rješenje. 95 | :::cautionoprez 96 | Ova dinamika dat će odgovor na pitanje: Može li se pohlepnim pristupom dobiti točno rješenje za razmjenu bilo koje količine novca ako su nam na raspolaganju zadane novčanice. 97 | 98 | Pošto zadatci uglavnom imaju testne primjere s raznim skupovima novčanica, nije nužno da ako pohlepno rješenje radi za neki skup novčanica da radi i za ostale skupove. 99 | ::: 100 | 101 | Pogledajmo malu promjenu u ograničenjima u zadatku. Neka vrijedi $x < 1e9$. Možemo primijetiti da u tom slučaju gore navedeno rješenje nije točno. Ne možemo napraviti polje veličine $1e9$, a i kad bismo imali dovoljno memorije i dalje bi rješenje bilo presporo. Tu nam je korisno znati pohlepni pristup. Primijetimo da, ako je najveća novčanica veličine $1e4$ tada je očito da ćemo sve iznad $2e4$ razmjenjivati upravo s tom novčanicom. Ako vam nije jasno zašto je to tako ponovno pročitajte poglavlje *Pohlepni pristupi*. 102 | 103 | Rješenje se tada svodi na primjenu redukcije na početni $x$ dok ne bude u intervalu $[max(a_i), 2 * max(a_i)>$. Tu redukciju moguće je napraviti u složenosti $O(1)$ jednostavnom matematikom. 104 | Analizirajmo složenost rješenja u tom slučaju. Tada ćemo dinamiku provoditi na maksimalno $2 * max(a_i)$. Što nam složenost svodi na $O(2 * max(a_i) * k)$. Sad imamo dva slična rješenja čija se složenost skalira s različitim faktorima, u jednom rješenju taj je faktor $x$, a u drugom $max(a_i)$. Pa možemo koristiti ono rješenje koje ima manji faktor kako bi dobili brži kod. -------------------------------------------------------------------------------- /docs/matematika/vazne-formule.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Važne formule 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Najveći zajednički djelitelj 12 | 13 | Odredimo najveći zajednički djelitelj(dalje u tekstu **GCD** od eng. Greatest Common Divisor) od $a$ i $b$. Možemo krenuti od $min(a, b)$ prema $1$ tražeći prvi broj s kojim su oba djeljiva. Složenost ovog postupka je $O(min(a, b))$, odnosno složenost je linearna. 14 | 15 | Naravno postoji i brži način određivanja GCD, a riječ je o Euklidovom algoritmu. Prvo ćemo pokazati kod, a nakon toga objasniti o čemu je riječ. 16 | 17 | ```cpp 18 | int GCD(int a, int b){ 19 | if(b == 0) return a; 20 | return GCD(b, a % b); 21 | } 22 | ``` 23 | 24 | **Dokaz točnosti algoritma.** 25 | 26 | 27 | 28 | Dokaz Euklidovog algoritma nije trivijalan, a možete ga pronaći na ovom linku 29 | 30 | Složenost algoritma je $O(\log min(a, b))$. Kod možete pisati i iterativno, u obliku while petlje, kako bi izbjegli pozivanje rekurzije ako je _time limit_ na zadatku strog. 31 | 32 | ### Najmanji zajednički višekratnik 33 | 34 | Odredimo najmanji zajednički višekratnik(dalje u tekstu **LCM** od engl. Least Common Multiple) od $a$ i $b$. U ovom slučaju možemo tražiti od $max(a, b)$ do $a * b$, no složenost tog postupka je $O(a * b)$, na svu sreću postoji brži način računanja LCM. LCM možemo izračunati u složenosti Euklidovog algoritma koristeći sljedeću formulu: $LCM(a, b) = a * b / GCD(a, b)$. **Pazite na cast u _long long_**. 35 | 36 | #### Dokaz točnosti formule 37 | 38 | GCD dvaju brojeva je broj čiji je rastav na proste faktore presjek rastava na proste faktore tih dvaju brojeva. 39 | 40 | LCM dvaju brojeva je broj čiji rastav na proste faktore kao podskup sadržava rastav na proste faktore od oba broja. 41 | 42 | Kada pomnožimo dva broja zapravo napravimo uniju njihovih prostih faktora. U toj uniji presjek(GCD) se očito nalazi $2$ puta(jednom zbog svakog broja). Uklanjanjem jednog presjeka dobivamo upravo LCM. 43 | 44 | ### Potenciranje i brzo potenciranje 45 | 46 | Želimo li izračunati $x^n$ možemo koristiti ugrađenu funkciju _double pow(base, exp)_. Nažalost korištenje te funkcije često nije dovoljno jer može prouzročiti bitan problem. Često se u zadatku traži da se ispiše ostatak pri dijeljenju rješenja sa $1e9 + 7$. Ako su ulazni parametri veliki moguće je da će doći do overflowa tijekom računanja pa formula $pow(x, n) \% mod$ neće dati točno rješenje. 47 | 48 | Problem je moguće riješiti tako da napišemo vlastitu funkciju za potenciranje koja podržava računanje s ostatkom pri dijeljenju. Ta bi funkcija izgledala ovako. 49 | 50 | ```cpp 51 | //NAPOMENA: smatramo da su svi brojevi integeri 52 | int pow_fixed(int base, int power, int mod){ 53 | int ret = 1; 54 | for(int i = 0; i < power; i++){ 55 | ret *= base; 56 | ret %= mod; 57 | } 58 | } 59 | ``` 60 | 61 | Složenost ove funkcije je $O(power)$. Tu je funkciju moguće ubrzati ako primijetimo jedno korisno svojstvo potencija, a to je ako je potencija parna, tada broj možemo kvadrirati, a potenciju prepoloviti. $x^{2n} = x^{2^{n/2}}$. Sada našu funkciju možemo prilagoditi tako da provjeravamo parnost potencije i primijenimo gornju formulu. 62 | 63 | ```cpp 64 | int brzo_potenciranje(int base, int power, int mod){ 65 | if(power == 0) return 1; 66 | if(power == 1) return base; 67 | // ako je potencija parna primijenimo gornju formulu 68 | if(power % 2 == 0) return brzo_potenciranje(base * base % mod, power / 2, mod); 69 | //inače primijenimo običan postupak 70 | return (brzo_potenciranje(base, power - 1, mod) * base) % mod; 71 | } 72 | ``` 73 | 74 | Složenost ovog algoritma je $O(\log power)$, no ovaj kod nam i dalje može stvarati neke probleme jer neki brojevi mogu _overflowati_, a i kod s puno modova nije baš lijep. Kako bismo izbjegli često _castanje_ u _long long_ i često pisanje mod koristimo nešto što je praksa u natjecateljskom programiranju, a to su vlastite funkcije za zbrajanje i množenje. To na prvu možda zvuči čudno i nepotrebno, no tako će naš kod izgledati ljepše, a i imat će bolje performanse (kasnije ćemo objasniti zašto). 75 | 76 | ```cpp 77 | int add(int a, int b, int mod){ 78 | int ret = a + b; 79 | if(ret >= mod) return ret - mod; 80 | if(ret < 0) return ret + mod; 81 | return ret; 82 | } 83 | int mul(long long int a, long long int b, int mod){ 84 | return (a * b) % mod; 85 | } 86 | ``` 87 | 88 | Sada možemo jednostavno prilagoditi funkciju za brzo potenciranje. 89 | 90 | ```cpp 91 | int brzo_potenciranje(int base, int power, int mod){ 92 | if(power == 0) return 1; 93 | if(power == 1) return base; 94 | // ako je potencija parna primijenimo gornju formulu 95 | if(power % 2 == 0) return brzo_potenciranje(mul(base, base, mod), power / 2, mod); 96 | //inače primijenimo običan postupak 97 | return mul(brzo_potenciranje(base, power - 1, mod), base, mod); 98 | } 99 | ``` 100 | 101 | Sada kod izgleda nešto ljepše. Ono što nam je važnije od izgleda koda je njegova efikasnost. Funkcija ostatka pri dijeljenju, iako je konstante složenosti, ima veliku konstantu, odnosno funkcija je spora. Upravo zato nam je cilj ne koristiti tu funkciju ako nije potrebno. Možete primijetiti da u funkciji _add()_ uopće ne koristimo ostatak pri dijeljenju, već isti problem rješavamo zbrajanjem i oduzimanjem što su brže funkcije od modanja. U funkciji _mul_ to ne radimo jer ćemo u množenju često imati overflow pa bi nam ta provjera dodatno usporila program. 102 | -------------------------------------------------------------------------------- /docs/matematika/najblizi-par-tocaka.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Najbliži par točaka 3 | --- 4 | 5 | ### Problem 6 | 7 | 8 | 9 | Zadano je $N$ točaka u ravnini. Treba pronaći udaljenost najbližeg para među njima. 10 | 11 | Ovo, naravno, možemo napraviti računajući udaljenost za svaki par točaka u vremenu $O(N^2)$. Ispostavlja se da za male $N$ to može biti optimalan pristup, ali za veće $N$ postaje prespor. Dolje opisano, bolje rješenje koristi _sweep line_ algoritam i ima složenost $O({N}\log{N})$ (a sličnu ideju možemo primijeniti i na _divide-and-conquer_ algoritam iste složenosti, ali malo kompliciranije implementacije). 12 | 13 | ### Rješenje 14 | 15 | Zamislimo vertikalni pravac koji prolazi ravninom slijeva nadesno, drugim riječima, sortirajmo točke po x-koordinati. Pamtimo vrijednost $d$ - minimalnu udaljenost nekog para među dosad pregledanim točkama. Za svaku točku na koju pravac naiđe, provjeravat ćemo čini li ona, s nekom od točaka koje smo već prošli, najbliži par dosad. 16 | 17 | Neka je trenutna točka $(x,y)$. Ako njoj slijeva postoji točka udaljena za manje od $d$, x-koordinata te točke mora biti u intervalu $[x-d,x]$, a y-koordinata u $[y-d,y+d]$. Stoga je dovoljno razmotriti samo točke u skupu $[x-d,x] \times [y-d,y+d]$. Efikasnost algoritma zasniva se na činjenici da ih taj skup uvijek sadrži $O(1)$. 18 | 19 | #### Zašto je točaka koje treba provjeriti $O(1)$? 20 | 21 | Kako znamo da je u $[x-d,x] \times [y-d,y+d]$ uvijek samo $O(1)$ već posjećenih točaka? Taj skup je pravokutnik sa stranicama duljina $d$ i $2d$. Zamislimo da smo ga podijelili na 8 regija (podjelom kraće stranice na 2, a dulje na 4 jednaka dijela), 8 kvadrata stranica duljine $\frac{d}{2}$. Svaki kvadrat može sadržavati najviše jednu točku, jer kad bi sadržavao barem dvije, njihova udaljenost bila bi maksimalno $\frac{d}{\sqrt{2}}$ (dijagonala kvadrata), što je manje od $d$, po pretpostavci dosad najmanje udaljenosti nekog para. To je kontradikcija, pa se u pravokutniku nalazi najviše 8 kandidata (zapravo i manje, no dovoljno je tvrdnju pokazati za 8). 22 | 23 | U implementaciji ćemo zapravo pamtiti skup "aktivnih" točaka - onih kojima je x-koordinata u intervalu [x-d,x], a y-koordinata bilo kakva - pa će pravokutnik koji nas zanima biti podskup ovog skupa. 24 | 25 | #### Dodavanje i brisanje aktivnih točaka 26 | 27 | Za brzinu algoritma ključno je da se operacije ubacivanja i izbacivanja točaka u skup aktivnih izvršavaju efikasno. To možemo postići pamćenjem strukture _binary search tree_ (sortiranog po y-koordinati) aktivnih točaka. Za svaku točku na koju naiđemo _sweep_-om trebamo ažurirati aktivne: prolazimo listom dotad aktivnih točaka i brišemo ih slijeva sve dok se po x-koordinati razlikuju od trenutne za više od $d$. Trenutnu točku u aktivne ćemo dodati tek na kraju, nakon provjere udaljenosti s ostalima. 28 | 29 | Zašto sortiramo po y-koordinati? Zbog načina na koji pamtimo skup aktivnih, sve točke u njemu imat će x-koordinatu manje od $d$ udaljenu od trenutne točke, pa ćemo kasnije, tijekom mjerenja udaljenosti, birati samo točke koje zadovoljavaju analogan uvjet za y-koordinatu. Po njoj sortiramo skup da bi takve točke bile uzastopne. 30 | 31 | U C++-u je implementacija olakšana postojanjem tipa podatka _set_ implementiranog kao _binary search tree_, dakle, operacije dodavanja i brisanja odvijaju se u logaritamskom vremenu i čuvaju sortiranost. 32 | 33 | ### Implementacija i analiza složenosti 34 | 35 | Sljedeći isječak koda koristi strukturu tocka uvedenu u ranijem poglavlju. Budući da koristimo set _struct_-ova, potrebno je definirati operator "\<" za njihovo uspoređivanje kako bi se mogle držati sortiranima (po y-koordinati) u setu. 36 | 37 | ```cpp 38 | bool operator<(const tocka& t1, const tocka& t2){ 39 | if(t1.y==t2.y) return t1.x aktivne; 53 | aktivne.insert(t[0]); 54 | int lijevo = 0; 55 | 56 | for (int i=1;i d) 60 | aktivne.erase(t[lijevo++]); 61 | 62 | /* početak iteracije, najljevija i najdonja moguća točka, lower bound za dio aktivnih kojim iteriramo */ 63 | tocka p; 64 | p.x=t[i].x-d; 65 | p.y=t[i].y-d; 66 | 67 | /* kraj iteracije: kraj seta ili su daljnje y-koordinate predaleko */ 68 | set::iterator it; 69 | for(it=aktivne.lower_bound(p); !(it==aktivne.end() || it->y > t[i].y+d); ++it) 70 | d = min(d, sqrt(pow(t[i].y - it->y, 2.0)+pow(t[i].x - it->x, 2.0))); 71 | 72 | aktivne.insert(t[i]); 73 | } 74 | return d; 75 | } 76 | ``` 77 | 78 | Poznato je da funkcija _sort_ ima vremensku složenost $O({N}\log{N})$. 79 | 80 | Svaka će točka točno jednom biti ubačena i izbačena iz _aktivnih_, pa _while_ petlja u cijelom programu izvršava $N$ izbacivanja. Funkcija _erase_ ima složenost $O(\log{N})$, pa je ukupna složenost ovog dijela $O({N}\log{N})$. 81 | 82 | U unutarnjoj _for_ petlji, pronalaženje _lower_bound_-a također traje $O(\log{N})$, a petlja prolazi kroz $O(1)$ točaka. Nju vanjska petlja poziva $N$ puta, pa je složenost ovog dijela opet $O({N}\log{N})$. 83 | 84 | I funkcija _insert_ ima složenost $O(\log{N})$, a poziva se na svakoj od $N$ točaka, što daje $O({N}\log{N})$. 85 | 86 | Ukupna vremenska složenost je $O({N}\log{N})$. 87 | -------------------------------------------------------------------------------- /docs/algoritmi-nad-grafovima-2/LCA.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LCA 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Uvod 12 | 13 | Nekad funkcije možemo zapisati pomoću usmjerenih grafova. Tada svaki vrh ima izlazni stupanj točno 1. Ako ne postoji nijedan ciklus duljine veće od 1, postojat će barem jedna petlja. Prvi nasljednik nekog vrha definiramo kao jedini njemu susjedan vrh. Možemo napraviti kompoziciju funkcije, pa nas tako zanima drugi nasljednik, treći nasljednik, pa čak i $10^{18}$-ti nasljednik. Rješenje je da iterativno izračunamo svaki $2^n$-ti nasljednik. Tada brzim potenciranjem možemo izračunati svaki $m$-ti nasljednik u složenosti $O(\log m)$. 14 | 15 | ### Problem 16 | 17 | Ako je stablo ukorijenjeno, za svaki vrh postoji točno jedan vrh koji nazivamo njegovim roditeljem. Za korijen stabla možemo reći da nema korijen, ili kažemo da je on sam sebi roditelj. Predak nekog vrha definiramo ovako: 18 | 19 | - ako je vrh *x* roditelj vrha *y*, tada kažemo i da je *x* (prvi) predak od *y* 20 | - ako je vrh *x* ($n$-ti) predak vrha *y*, a *y* je roditelj vrha *z*, tada je vrh *x* ($n+1$-ti predak) vrha *z* 21 | 22 | Svaka dva vrha imaju zajedničke podskupove predaka, tj. za svaka dva vrha *u* i *v* postoji njima zajednički predak *x* takav da je svaki predak od *x* ujedno i predak od *u* i *v*. 23 | 24 | Želimo za neka dva vrha saznati koji im je **najniži zajednički predak** (engl. *lowest common ancestor*, *LCA*) u stablu, odnosno koji im je zajednički predak najudaljeniji od korijena stabla. 25 | 26 | ![Primjer najnižeg zajedničkog predka](/img/lca.png) 27 | 28 | Na slici je tamno zeleno obojan najniži zajednički predak vrhova *x* i *y*. Svijetlo zeleni vrhovi predstavljaju zajedničke predke vrhova *x* i *y* koji nisu najniži. 29 | 30 | ### Primjena 31 | 32 | Za svaka tri vrha *x*, *y* i *z* u nekom grafu *G* postoji vrh *m* takav da je on sadržan u svakom putu između neka dva vrha među *x*, *y* i *z*. Ako imamo neke *x*, *y* i *z*, možemo izračunati `lca(x,y)`, `lca(x,z)`, `lca(y,z)` i `lca(x,lca(y,z))`. Barem tri od njih bit će isti vrh. Također vrijedi `lca(x,lca(y,z)) == lca(y,lca(x,z)) == lca(z,lca(x,y))`. 33 | 34 | Neka smo za svaki vrh izračunali njegovu dubinu, odnosno njegovu udaljenost od korijena. Označimo dubinu vrha *x* sa `dep[x]`. Tada je udaljenost nekog vrha *x* od vrha *y* jednaka: `dep[x] + dep[y] - 2 * dep[lca(x,y)]`. 35 | 36 | ### Rješenje u logaritamskoj složenosti 37 | 38 | Dovoljno je za svaki vrh izračunati njegov $2^i$-ti predak, za svaki nenegativni cijeli broj $i$ manji ili jednak $\log_{2}n$, gdje je $n$ broj vrhova u grafu. Reći ćemo da je korijen sam sebi roditelj. 39 | 40 | Algoritam je opisan u donjem kodu. 41 | 42 | ```cpp 43 | #include 44 | using namespace std; 45 | 46 | const int N = 1e5; 47 | const int LOG = 22; 48 | 49 | int n; 50 | // a[i][j] pamti 2^i-ti predak od j 51 | // a[0][j] predstavlja roditelja od j 52 | int a[LOG][N]; 53 | int dep[N]; 54 | 55 | // lca(u, v) == lca(v, u) 56 | int lca(int u, int v) 57 | { 58 | if (dep[u] < dep[v]) swap(u, v); 59 | // sada je sigurno dep[u] >= dep[v] 60 | 61 | // ako u i v nisu na istoj razini (udaljenosti od korijena), tada je x-ti predak od u jednak x-tom predku od v samo ako se radi o korijenu 62 | for (int i = LOG - 1; i >= 0; i--) { 63 | // svaki broj ima jedinstveni binarni zapis 64 | // dižemo vrh u na istu razinu kao što je vrh v 65 | // to jest, dižemo vrh u za dep[u] - dep[v] razina 66 | if (dep[a[i][u]] >= dep[v]) 67 | u = a[i][u]; 68 | } 69 | 70 | // dva vrha se nikada ne podudaraju ako nisu na istoj razini 71 | // dep[lca(u,v)] <= min(dep[u], dep[v]) 72 | // ako smo dizanjem u do razine s v dobili da su u i v isti vrh, ispišemo ga, to nam je traženi LCA 73 | // ovo se dešava kada je u predak od v, ili je v predak od u 74 | if (u == v) return u; 75 | 76 | // dižemo u i v na istu razinu čim bliže korijenu, ali dokle god nisu isti 77 | for (int i = LOG - 1; i >= 0; i--) { 78 | if (a[i][u] != a[i][v]) { 79 | u = a[i][u]; 80 | v = a[i][v]; 81 | } 82 | } 83 | 84 | // maksimalno smo digli u i v na istu razinu bez da su isti 85 | // slijedi da su roditelji od u i v isti 86 | // a[0][u] = a[0][v], pa nam je svejedno koji vratimo, to je traženi LCA 87 | return a[0][u]; 88 | } 89 | 90 | int main() 91 | { 92 | scanf("%d", &n); 93 | // 2^0=1-ti predak od korijena je on sam 94 | a[0][1] = 1; 95 | // dubina nekog cvora je njegova udaljenost od korijena + 1 96 | dep[1] = 1; 97 | for (int i = 1; i <= n; i++) { 98 | int m; 99 | // čvor i ima m djece 100 | scanf("%d", &m); 101 | for (int j = 0; j < m; j++) { 102 | int ch; 103 | // unosimo dijete od čvora i 104 | scanf("%d", &ch); 105 | // roditelj čvora ch jest i 106 | a[0][ch] = i; 107 | // ch je za 1 udaljeniji od korijena nego i 108 | dep[ch] = dep[i] + 1; 109 | } 110 | } 111 | // pretprocesiramo matricu predaka 112 | for (int i = 1; i < LOG; i++) { 113 | // obavezno mora biti ovo iznad vanjska petlja a ovo ispod unutarnja 114 | for (int j = 1; j <= n; j++) { 115 | // ovo je ključno 116 | // 2^i-ti predak od j jest 2^(i-1)-ti predak od 2^(i-1)-tog predka od j 117 | a[i][j] = a[i - 1][a[i - 1][j]]; 118 | } 119 | } 120 | int q; 121 | // postavljamo q upita o najnižim zajedničkim predcima 122 | scanf("%d", &q); 123 | for (int i = 0; i < q; i++) { 124 | int u, v; 125 | // zanima nas najniži zajednički predak vrhova u i v 126 | scanf("%d%d", &u, &v); 127 | // ispisujemo lca(u, v) koja ga računa 128 | printf("%d\n", lca(u, v)); 129 | } 130 | return 0; 131 | } 132 | ``` -------------------------------------------------------------------------------- /docs/potpuno-pretrazivanje-i-pohlepni-pristupi/pruning.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pruning 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | **Pruning** je tehnika ranijeg odbacivanja pokušaja koji sigurno neće 10 | rezultirati rješenjem, a termin se koristi primarno kod rekurzivnih istraživanja. 11 | 12 | U sljedećem problemu demonstrirat ćemo korištenje pruninga, a uz to 13 | pokazati tehniku pametne predaje stanja iz jednog rekurzivnog poziva u drugi. 14 | 15 | ### Problem 2: Broj puteva koji pokrivaju polje 16 | 17 | U prvom retku zadana su dva broja, $n$ i $m$, za koje vrijedi $2 \leq n,m \leq 6$. 18 | Ta dva broja definiraju dimenzije šahovskog polja, širinu i dužinu. 19 | 20 | Odredi koliko puteva postoji koji kreću s jednog kuta i završavaju u dijagonalnom, 21 | a svakim poljem prolaze **točno jednom**. 22 | 23 | #### Pruning 24 | 25 | Razmislite li je li moguće završiti put koji nije posjetio sva polja, 26 | a stao je na krajnje? Naravno, nije. 27 | Dakle, svi rekurzivni pozivi koji nalete na takvu situaciju trebaju biti 28 | prekinuti bez daljnjeg razmotavanja. 29 | 30 | Još jedan primjeru pruninga je taj da u razvoju rekurzije nikad ne stanemo na polje 31 | na koje smo već stali; iako je ovo nužno za dobar algoritam i vjerojatno očito, 32 | također spada u tehniku pruninga. 33 | 34 | #### Kako provjeriti gdje smo prošli? 35 | 36 | Svaki rekurzivni poziv mora biti svjestan svoje prošlosti, 37 | to jest simulaciji kojeg puta on odgovara (koja su polja već posjećena, poredak nije bitan). 38 | Ovo moramo postići nekom varijantom dvodimenzijskog polja u kojem su označena polja koja smo prošli. 39 | Naravno, kopiranje tog polja minimalno promijenjenog iz jednog poziva u drugi je jako vremenski zahtijevno. 40 | 41 | Umjesto kopiranja, možemo samo promijeniti jedan element i poslati referencu, 42 | a kad se rekurzija izmota iz tog poziva, taj element vratiti na staru vrijednost. 43 | U sljedećem rekurzivnom pozivu će se promijeniti neki drugi element, 44 | a nakon njega će se i taj drugi element vratiti na staro. 45 | Kad se cijeli rekurzivni poziv završi, rekurzija će se izmotati natrag, 46 | gdje će roditelj poziva napraviti istu stvar. 47 | 48 | Postizanje ovakvog efekta zahtijeva da struktura (polje posjećenih polja) 49 | bude reverzibilna, tj. da na njoj možemo napraviti promijene i onda ih poništiti. 50 | 51 | #### Kako provjeriti jesmo li prošli sva polja? 52 | 53 | Već smo napomenuli da stajanje na zadnje polje bez da smo posjetili sva ostala 54 | nužno rezultira granom rekurzije koju treba podrezati (nema rješenja u toj grani). 55 | 56 | Kako provjeriti da smo prošli sva polja bez iteracije po dvodimenzijskom polju? 57 | Jednostavno, pozivi rekurzije pratit će koliko su polja posjetili, 58 | to će biti još jedan argument rekurzije. 59 | 60 | #### Implementacija 61 | 62 | Povratna vrijednost rekurzije označavat će broj traženih puteva nađenih u toj rekurzivnoj grani. 63 | 64 | ```cpp 65 | #include 66 | using namespace std; 67 | 68 | int rek(int i, int j, vector>& I, int visited_cnt, int n, int m){ 69 | // na krajnjem smo polju 70 | if (i == n-1 && j==m-1){ 71 | // pruning, ovakve grane nemaju rjesenja 72 | if (visited_cnt != n*m){ 73 | return 0; 74 | } 75 | // rjesenje! 76 | else{ 77 | return 1; 78 | } 79 | } 80 | 81 | 82 | int paths = 0; 83 | // zgodan trik za situacije s kretanjima po polju 84 | vector> dirs = {{-1,0},{1,0},{0,-1},{0,1}}; 85 | 86 | for (auto dir : dirs){ 87 | int ti = i+dir[0]; 88 | int tj = j+dir[1]; 89 | 90 | // unutar polja i nije posjećen 91 | if (ti>=0 && tj>=0 && ti>n>>m; 103 | 104 | // polje posjećenih polja 105 | vector> I(n,vector(m)); 106 | 107 | // vidi opis 108 | I[0][0] = true; 109 | cout << rek(0,0,I,1,n,m) << endl; 110 | return 0; 111 | } 112 | ``` 113 | 114 | Primjetite da smo se u ovoj implementaciji držali sljedeće *filozofije*: 115 | 116 | - *svaki rekurzivni poziv na svom početku ima dobre vrijednosti polja $\texttt{I}$ i broja $\texttt{visited\_cnt}$* 117 | 118 | Iako to nije moralo biti tako (sam poziv je to mogao ažurirati), 119 | treba unaprijed odlučiti i držati se toga u cijelom programu. 120 | Implikacija našeg izbora ujedno je i postavljanje vrijednosti polja u mainu. 121 | 122 | :::tipsavjet 123 | 124 | Prethodno spomenute *filozofije* u žargonu računalne znanosti zovemo 125 | [invarijantama](https://en.wikipedia.org/wiki/Invariant_(mathematics)#Invariants_in_computer_science) 126 | 127 | ::: 128 | 129 | #### Simetrija 130 | 131 | Ograničimo problem na takav da su $n$ i $m$ jednaki. 132 | 133 | Primijetite da se u rješenjima može naći simetrija: 134 | - za svako rješenje koje kreće u u jednom od dva dostupna smjera, 135 | postoji drugo rješenje koje kreće u onom drugom smjeru, a ta rješenja 136 | se mogu dobiti jedno od drugoga zrcaljenjem 137 | 138 | Dakle, umjesto da istražujemo oba puta, mi ćemo ručno krenuti jednim, 139 | a zatim rezultat pomnožiti s $2$. 140 | 141 | Implementacija se razlikuje samo u dijelu funkcije $\texttt{main}$: 142 | 143 | ```cpp 144 | I[0][0] = true; 145 | I[0][1] = true; 146 | cout << rek(0,1,I,2,n,m)*2 << endl; 147 | return 0; 148 | ``` 149 | 150 | Ova jednostavna optimizacije uštedjet će nam pola vremena! 151 | U drugim postavkama i zadatcima postoje i složenije simetrije koje nećemo istraživati. 152 | -------------------------------------------------------------------------------- /docs/sortiranje-i-pretrazivanje/ternarno-pretrazivanje.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ternarno pretraživanje 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ## O algoritmu 12 | 13 | Kao što smo vidjeli u prethodnom članku, binarno pretraživanje pri svakom koraku dijeli niz koji pretražujemo na **dva** podniza te pomoću uvjeta određuje u kojem podnizu nastavljamo pretragu. Što se dogodi ako podniz podijelimo na **tri** dijela? 14 | 15 | Algoritam ternarnog pretraživanja gotovo se potpuno podudara s binarnim te i njega koristimo za pronalazak broja $x$ u **sortiranom** nizu brojeva. Umjesto jedne srednje vrijednosti $mid$ ovoga puta biramo dvije i to na sljedeći način: 16 | 17 | ```code 18 | mid1 = l + (r-l)/3 19 | mid2 = r – (r-l)/3 20 | ``` 21 | 22 | gdje su $r$ i $l$ desna i lijeva granica trenutnog podniza. 23 | 24 | Sada uspoređujemo $x$ s vrijednostima $array[mid1]$ i $array[mid2]$. Ako je $x$ različit od obje vrijednosti (nismo pronašli rješenje), imamo tri slučaja: 25 | 26 | 1. $x$ je manji od $array[mid1]$ : $x$ se (ako ga ima) nalazi u intervalu $[l,mid1\rangle$ 27 | 2. $x$ je veći od $array[mid2]$ : $x$ se (ako ga ima) nalazi u intervalu $\langle mid2,r]$ 28 | 3. inače: $x$ se nalazi u intervalu $\langle mid1,mid2 \rangle$ 29 | 30 | Varijable $r$ i $l$ mijenjamo ovisno o slučaju koji je nastupio i ponavljamo postupak dok ne pronađemo rješenje ili provjerimo cijeli niz. Povučemo li analogiju s binarnim pretraživanjem, problem se ovdje pri svakom koraku svodi na $1/3$ prethodnog problema pa je složenost algoritma $O(log_3(n))$. 31 | 32 | Ovoga ćemo puta pokazati rekurzivnu implementaciju (slična se može napraviti i za binarnu pretragu). Rekurzija vraća poziciju u nizu na kojoj se nalazi traženi broj ili $-1$ u slučaju da tog broja nema u nizu: 33 | 34 | ```cpp 35 | int ternary_search(int l, int r, int x) { 36 | if (r >= l) { 37 | int mid1 = l + (r-l)/3; 38 | int mid2 = r - (r-l)/3; 39 | 40 | if (array[mid1] == x) 41 | return mid1; 42 | if (array[mid2] == x) 43 | return mid2; 44 | if (x < array[mid1]) 45 | return ternary_search(l, mid1-1, x); 46 | else if (x > array[mid2]) 47 | return ternary_search (mid2+1, r, x); 48 | else 49 | return ternary_search(mid1+1, mid2-1, x); 50 | } 51 | return -1; 52 | } 53 | ``` 54 | 55 | ## Implementacija na realnom intervalu 56 | 57 | Ponekad želimo raditi pretraživanje na intervalu realnih brojeva. Ako koristimo ranije navedenu implementaciju, mogu se dogoditi pogreške pri uspoređivanju brojeva zato što računala nemaju beskonačnu preciznost i uvijek zaokružuju vrijednosti na nekoj decimali. Iz tog razloga nećemo provjeravati 'jednakost' dvaju brojeva nego razlikuju li se oni **do na neki mali faktor**. Prethodni bi se primjer mogao modificirati na sljedeći način: 58 | 59 | ```cpp 60 | double epsilon = 1e-7; 61 | ... 62 | double ternary_search(double l, double r, double fx) { 63 | if (r-l > epsilon) { 64 | double mid1 = l + (r-l)/3; 65 | double mid2 = r - (r-l)/3; 66 | 67 | if (abs(f(mid1) - fx) < epsilon) 68 | return mid1; 69 | ... 70 | } 71 | } 72 | ``` 73 | 74 | ## Lokalni ekstremi 75 | 76 | U prethodnom smo primjeru proveli ternarnu pretragu nad sortiranim nizom brojeva, ali treba imati da umu da je za takve slučajeve binarna pretraga najčešće sasvim dovoljna (u kontekstu brzine algoritma). Ipak, ternarna je pretraga našla svoju primjenu u pronalaženju lokalnih ekstrema funkcija! 77 | 78 | Zamislimo neku funkciju koja ima samo jedan ekstrem i neka je to **maksimum**. Zadatak nas pita koja točka na $x$-osi odgovara traženom maksimumu. Primjer takve funkcije prikazan je na slici. Maksimum funkcije postiže se u točki $A$. Za sve $x$-eve koji se nalaze lijevo od točke $A$ možemo reći da se nalaze u _rastućem dijelu funkcije_, dok za sve koji se nalaze desno možemo reći da se nalaze u _padajućem dijelu_. 79 | 80 | ![unimodal function](../../static/img/sortiranje_unimodal_function.png)
81 | 82 | Kao i ranije, dijelimo interval na tri dijela uz pomoć $mid1$ i $mid2$ samo ovoga puta uspoređujemo vrijednosti funkcije u tim točkama. Mogućnosti su da se obje vrijednosti nalaze u rastućem dijelu, obje u padajućem dijelu ili po jedna u svakom od ta dva (prilikom razmišljanja o slučajevima razmislite o sve tri mogućnosti). Razlikujemo tri slučaja: 83 | 84 | 1. $f(mid1)f(mid2)$ : Maksimum se **ne** može nalaziti desno od $mid2$ ($mid2$ se sigurno nalazi u padajućem dijelu), nastavljamo pretragu u intervalu $[l,mid2]$ . 86 | 3. $f(mid1)=f(mid2)$ : Maksimum se nalazi negdje između $mid1$ i $mid2$ ($mid1$ je u rastućem dijelu, a $mid2$ u padajućem). 87 | 88 | Više o ovoj temi pročitajte [ovdje](https://cp-algorithms.com/num_methods/ternary_search.html). 89 | 90 | :::noteprimijetite 91 | Ovaj će algoritam raditi samo na funkcijama koje imaju isključivo **jedan** ekstrem. Takve funkcije nazivmo [unimodalnima](https://en.wikipedia.org/wiki/Unimodality#Unimodal_function). 92 | ::: 93 | 94 | ### Što ako funkcija ima više od jednog ekstrema? 95 | 96 | Traženje ekstrema funkcija ima veliku primjenu u znanosti i statističkoj obradi podataka, a često je bitan alat za numeričko rješavanje jednadžbi. Zamislite da ste izmjerili neke podatke i želite iz izračunate krivulje dobiti u kojoj se točno točki nalazi ekstrem funkcije. Rješenje problema zvuči malo glupavo, ali radi. Ideja je da okom pogledate graf funkcije i odaberete neki interval oko ekstrema koji vas zanima tako da ne obuhvatite niti jedan drugi lokalni ekstrem te funkcije. Potom provedete ternarnu pretragu isključivo na **tom intervalu** i dobili ste traženu točku (sveopća radost). 97 | -------------------------------------------------------------------------------- /static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/sortiranje-i-pretrazivanje/binarno-pretrazivanje.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Binarno pretraživanje 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Zamislimo sljedeću situaciju. Dana je lista od $n$ brojeva koji su **sortirani** i zadatak nas pita nalazi li se u toj listi broj $x$? Prva ideja mogla bi biti prolazak po svim elementima liste pri čemu bismo svaki put radili usporedbu s brojem $x$ dok ga eventualno ne pronađemo. To nam daje algoritam složenosti $O(n)$. Postoji li brži način? Pomaže li nam ikako činjenica da je dobiveni niz brojeva sortiran? Tu na snagu stupa **binarno pretraživanje**. 12 | 13 | Vjerovali ili ne, s binarnim pretraživanjem već ste se više puta susreli u svakodnevnom životu (npr. traženje riječi u rječniku ili točne stranice u knjizi). Sigurno ste i barem jednom u životu igrali igru u kojoj osoba zamisli broj u nekom intervalu (npr. $1-100$), a vi morate pogoditi koji je broj zamislila. Prilikom svakog pokušaja osoba vam kaže je li broj koji je ona zamislila veći, manji ili jednak broju koji ste predložili. Kada biste ovu igru igrali optimalno, prvo biste pokušali sa $50$. Osoba će vam reći je li njezin broj veći ili manji i vi ste tako problem pogađanja jednog od $100$ brojeva prepolovili na samo $50$ mogućih rješenja. Ako je npr. osoba rekla da je njezin broj veći, lako je zaključiti da je idući optimalan korak pretpostaviti broj $75$ čime je problem ponovo prepolovljen. Budući da se složenost problema prilikom svakog koraka prepolavlja, ukupna će složenost odgovarati **$O(log_2(n))$** (što je za velike $n$-ove punopuno manje od linearne). 14 | 15 | Vratimo se sada na početni problem - nalaženje broja $x$ u nizu od n brojeva. Primjenjujemo istu logiku kao i u prethodnom primjeru samo ćemo ovoga puta binarno pretraživati poziciju na kojoj se nalazi traženi broj. U prvom koraku uspoređujemo $x$ s brojem u sredini liste. Ako je $x$ manji od njega, pretraživanje nastavljamo u lijevoj polovici, u suprotnom pretražujemo desnu polovicu (princip rada je prikazan na animaciji ispod). 16 | 17 | ![binary](../../static/img/binary_search.gif )
18 | 19 | Ovo se rješenje može implementirani na sljedeći način: 20 | 21 | ```cpp 22 | // l i r su lijeva i desna granica intervala koji trenutno pretražujemo 23 | int l=0, r=n-1; 24 | int array[n]; 25 | 26 | while (l <= r) { 27 | int mid = (l+r)/2; 28 | if (array[mid] == x) { 29 | //pronašli smo broj x na poziciji mid 30 | } 31 | if (array[mid] > x) r = mid-1; 32 | else l = mid+1; 33 | } 34 | ``` 35 | 36 | Budući da svaki put prepolovimo interval pretraživanja, složenost je i ovdje $O(log_2(n))$. 37 | 38 | Alternativni način implementacije binarne pretrage je pomoću 'koraka'. Ideja je da na početku radimo veće korake, a kako se približavamo odgovoru koraci se prepolavljaju. 39 | 40 | ```cpp 41 | int k = 0; //trenutna pozicija u listi 42 | for (int b = n/2; b >= 1; b /= 2) { 43 | //ako s korakom ne bi preskočili broj, napravimo korak 44 | while (k+b < n && array[k+b] <= x) k += b; 45 | } 46 | if (array[k] == x) { 47 | //pronašli smo broj x na poziciji mid 48 | } 49 | ``` 50 | 51 | 52 | ## C++ funkcije 53 | 54 | Standardne C++ biblioteke sadrže implementirane funkcije koje ponekad mogu zamijeniti binarnu pretragu. Sve također rade u logaritamskoj složenosti. 55 | 56 | - **lower_bound** - vraća pointer na prvu vrijednost u listi koja je **barem $x$** 57 | - **upper_bound** - vraća pointer na prvu vrijednost u listi koja je **veća od $x$** 58 | - **equal range** - vraća oba navedena pointera 59 | 60 | Ako traženog broja $x$ nema u listi, funkcije vraćaju pointer na .end() element. 61 | 62 | ```cpp 63 | vector v; //... npr. [5,5,5,6,6,6,7,7] 64 | 65 | vector::iterator lower, upper; 66 | lower = lower_bound(v.begin(), v.end(), 6); 67 | upper = upper_bound(v.begin(), v.end(), 6); 68 | 69 | cout << "lower_bound: " << (lower-v.begin()) << '\n'; // lower_bound: 3 70 | cout << "upper_bound: "<< (upper-v.begin()) << '\n'; // upper_bound: 6 71 | ``` 72 | 73 | :::cautionoprez 74 | Navedene funkcije podrazumijevaju da je lista **sortirana**. 75 | ::: 76 | 77 | 78 | ## Primjene 79 | 80 | Kao što smo vidjeli u ranijim primjerima, binarno pretraživanje često koristimo kada provjeravamo je li neki broj u listi i, ako je, na kojoj je poziciji. Pogledajmo sada neke drugačije primjene ovog algoritma. 81 | 82 | ### Pretraživanje rješenja 83 | 84 | Zamislimo problem u kojem tražimo odgovarajuću veličinu kvadratnog kaveza za neki čopor majmuna u zoološkom vrtu. Želimo odabrati kavez tako da mu veličina bude minimalna, ali takva da svi majmuni budu zadovoljni (zadovoljstvo majmuna određeno je nekim uvjetima zadatka). Moguće je rješenje krenuti od veličine d=$1$ i povećavati veličinu za $1$ tako da pri svakom koraku provjeravamo jesmo li ispunili uvjet zadovoljstva (kad ga ispunimo, našli smo najmanju prihvatljivu veličinu i prestajemo izvršavati program). Ovo rješenje radi, ali je gotovo uvijek presporo. 85 | 86 | Pokušajmo sada upotrijebiti znanje binarne pretrage. Važno je primijetiti da sve veličine kaveza do neke granične veličine **ne** ispunjavaju uvjet zadovoljstva, a sve veličine nakon te ga ispunjavaju. Naš je zadatak pronaći upravo tu graničnu veličinu. Postavimo lijevu granicu pretrage na $1$, a desnu na neki veliki broj koji sigurno ispunjava uvjet zadatka. Pri svakom koraku pretrage provjeravamo jesmo li ispunili uvjet zadatka. Ako jesmo, pomičemo desnu granicu (želimo još manju veličinu), a ako nismo, pomičemo lijevu granicu. 87 | 88 | ![happy monkey](../../static/img/sortiranje_happy_monkey.jpg )
89 | Uistinu zadovoljan majmun. 90 | 91 | ### Maksimum funkcije 92 | 93 | Binarnu pretragu također možemo koristiti za traženje maksimuma funkcija koje prvo rastu, a potom padaju. Za takve funkcije vrijedi da je $f(x)f(x+1)$ za $x \geq k$. Mi tražimo poziciju $k$ koja odgovara maksimumu funkcije.: 94 | 95 | ```cpp 96 | int x = -1; 97 | for (int b = z; b >= 1; b /= 2) { 98 | while (f(x+b) < f(x+b+1)) x += b; 99 | } 100 | int k = x+1; 101 | ``` 102 | 103 | :::noteprimijetite 104 | U ovom slučaju uzastopne vrijednosti funkcije ne smiju biti jednake. Kada bi to bilo dozvoljeno, ne bismo znali kako nastaviti pretragu. 105 | ::: 106 | -------------------------------------------------------------------------------- /docs/matematika/convex-hull.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Convex hull 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Problem 12 | 13 | Zadano je $N$ točaka u ravnini. Treba pronaći njihovu "konveksnu ljusku", engl. _convex hull_, tj. najmanji konveksni poligon koji obuhvaća sve točke. 14 | 15 | Poligon je konveksan ako za svake dvije točke koje sadržava, sadržava i dužinu koja ih povezuje. Možemo ga karakterizirati i kao poligon kojem su svi kutovi manji od 180°. Traženje najmanjeg takvog povlači da će mu vrhovi biti neke od zadanih točaka, pa je problem upravo određivanje o kojim točkama se radi. Ako zamislimo da oko cijelog skupa rastegnemo gumicu i pustimo je da se stisne, njezin krajnji oblik ocrtavat će "ljusku" koju tražimo: 16 | 17 | 18 | 19 | ### Rješenje 20 | 21 | Algoritam opisan ovdje varijanta je Grahamovog skena, poznata kao Andrewov algoritam "monotonog lanca". 22 | 23 | Prvi korak je sortirati točke uzlazno po x-osi (ako je x-koordinata jednaka, uzlazno po y-osi) i tako odrediti krajnju lijevu točku, $A$, i krajnju desnu, $B$. Naravno, $A$ i $B$ moraju biti dio ljuske jer su po x-osi najudaljenije, pa ne mogu biti obuhvaćene stranicama poligona koje spajaju neke druge dvije točke. Nacrtajmo pravac kroz $A$ i $B$. Sada točke možemo podijeliti u dva skupa s obzirom na stranu pravca na kojoj se nalaze: $S_1$ koji sadrži točke "iznad" pravca, i $S_2$ koji sadrži točke "ispod" pravca. Ljusku ćemo konstruirati iz dvaju dijelova: gornjeg, za koji ćemo točke izabrati iz $S_1$, i donjeg, s točkama iz $S_2$. 24 | 25 | Dakle, daljnji tok algoritma svodi se na dvije važne provjere: određivanje s koje strane pravca $AB$ se nalazi neka točka, i određivanje točaka koje će činiti vrhove konveksne ljuske. 26 | 27 | #### Provjera položaja točke 28 | 29 | Da bismo odredili s koje strane $AB$ se nalazi neka točka $T$, tj. pripada li skupu $S_1$ ili $S_2$, potrebne su nam točke sortirane po x-koordinati, i pojam orijentacije uveden u ranijem poglavlju. Točke $A$ i $B$ smatrat ćemo elementima obiju skupova. Redom za svaku drugu točku $T$ provjeravamo je li orijentacija kuta između $\overrightarrow{TA}$ i $\overrightarrow{TB}$ _counterclockwise_, te ju smatramo elementom $S_1$ ako jest, odnosno $S_2$ ako nije (za točke koje su točno na pravcu svejedno je kojem skupu pripadaju). Možete se sami uvjeriti da _counterclockwise_ orijentacija odgovara položaju iznad $AB$, a _clockwise_ ispod. 30 | 31 | #### Konstrukcija konveksne ljuske 32 | 33 | Konstruirajmo najprije gornji dio, počevši dodavanjem točke $A$ u ljusku. Prolazimo redom elementima $S_1$ sortiranima po x-koordinati. Ako su u ljusci trenutno manje od dvije točke, dodamo trenutnu točku $T$. Ako su u ljusci barem dvije točke, provjeravamo kut između pravca kojeg čine predzadnja ($R$) i zadnja ($S$) dodana točka ljuske, i $T$. Sve dok orijentacija kuta $RST$ nije _counterclockwise_, uklanjamo zadnju točku iz ljuske, i na kraju dodamo trenutnu. 34 | 35 | Dok smo iznad pravca $AB$, "skretanje ulijevo" (kut u smjeru kazaljke sata) znači da će poligon sadržavati izbočeni kut, što je protivno konveksnosti, pa smo morali neku točku ukloniti. Ljuska sa samo predzadnjom i trenutnom točkom kao vrhovima obuhvaćat će i zadnju točku, jer orijentacija osigurava da je ona ispod ostale dvije, pa smo ju mogli izbaciti. 36 | 37 | Analogno konstruiramo i donji dio, ali provjeravamo je li orijentacija odgovarajućeg kuta _clockwise_. Konačna konveksna ljuska unija je gornjeg i donjeg dijela. 38 | 39 | ### Implementacija i analiza složenosti 40 | 41 | Sljedeći isječak koda koristi strukturu _tocka_ te funkcije _cw_ i _ccw_ uvedene u ranijem poglavlju. 42 | 43 | ```cpp 44 | bool cmp(tocka a, tocka b) { 45 |     if(a.x == b.x) return a.y < b.y; 46 |     return a.x < b.x; 47 | } 48 | 49 | void convex_hull(vector& tocke) { 50 |     if (tocke.size() == 1) 51 |         return; 52 | 53 |     sort(tocke.begin(), tocke.end(), cmp); 54 |     tocka A = tocke[0], B = tocke.back(); 55 |     vector gore, dolje; /* gornji i donji dio konveksne ljuske */ 56 |     gore.push_back(A); 57 |     dolje.push_back(A); 58 |     59 |     for (int i = 1; i < tocke.size(); i++) { 60 |         /* ako je trenutna tocka B, ili je iznad pravca AB */ 61 |         if (i == tocke.size() - 1 || ccw(A, tocke[i], B)) { 62 |             /* !ccw umjesto cw: uključujemo i slučaj kuta od 180° (ako je u nekom 'vrhu' kut od 180°, ta je točka element stranice, a ne vrh) */ 63 |             /* dok u ljusci postoje bar 2 točke, i vrijedi da trenutna točka ne skreće counterclockwise */ 64 |             while (gore.size() >= 2 && !ccw(gore[gore.size()-2], gore[gore.size()-1], tocke[i])) 65 |                 gore.pop_back(); 66 |             gore.push_back(tocke[i]); 67 |         } 68 |         if (i == tocke.size() - 1 || cw(A, tocke[i], B)) { 69 |             while(dolje.size() >= 2 && !cw(dolje[dolje.size()-2], dolje[dolje.size()-1], tocke[i])) 70 |                 dolje.pop_back(); 71 |             dolje.push_back(tocke[i]); 72 |         } 73 |     } 74 |   75 |     /* u ovoj implementaciji, vrhovi ljuske spremljeni su u isti vektor koji je čuvao početne točke, koje su izgubljene */ 76 |     tocke.clear(); 77 |     for (int i = 0; i < gore.size(); i++) 78 |         tocke.push_back(gore[i]); 79 |     for (int i = dolje.size() - 2; i > 0; i--) /* ne prepisujemo krajnje točke iz 'dolje' jer su A i B već prepisane iz 'gore' */ 80 |         tocke.push_back(dolje[i]); 81 | } 82 | ``` 83 | 84 | U dijelu konstrukcije ljuske nakon sortiranja, svaka se točka može u operacijama javiti samo dvaput. Na primjer, promotrimo skup $S_1$. Točkama prolazimo slijeva nadesno, pa na pojedinu točku prvi put nailazimo kada ju dodajemo u ljusku, a algoritam nastavlja dalje. Na istu točku se možemo vratiti samo kada utvrdimo da sa sljedećom zatvara kut u obrnutom smjeru od kazaljke na satu, a operacija u kojoj sudjeluje u tom slučaju je njeno uklanjanje, pa se ne može pojaviti opet. Slično vrijedi i za $S_2$, dakle, _for_ petlja ima složenost $O(N)$. 85 | 86 | Funkcija _sort_, s druge strane, ima složenost $O(N \log(N))$ i dominira ukupnim vremenom izvršavanja. Složenost cijelog algoritma zato je jednaka $O(N \log(N))$. 87 | -------------------------------------------------------------------------------- /docs/upiti-nad-intervalima-1/fenwickovo-stablo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fenwickovo stablo 3 | --- 4 | 5 | ### Uvod 6 | Fenwickovo stablo je struktura podataka koja omogućuje upite sumom prefiksa niza i promjenu vrijednosti elementa niza, sve u $O(log(n))$. Fenwickovo stablo je još poznato pod imenom binarno indeksirano stablo, iako zapravo nije ni stablo, već niz. 7 | Prije nego što objasnimo strukturu, upoznajmo se sa sljedećom operacijom. 8 | 9 | ### Izoliranje posljednjeg postavljenog bita 10 | Kao što naslov kaže, potrebno je iz nekog broja uzeti posljednji postavljeni bit, dakle bit čija je vrijednost 1. 11 | Npr, imamo broj 14. Ako ga prikažemo u bazi 2, on izgleda ovako: 0000 1110 12 | Ako izoliramo posljednji postavljeni bit, dobijemo 2 (0000 0010), a to se postiže bitovnom operacijom $AND(\&)$ s dvojnim komplementom broja. Brzo rješenje je sljedeće: 13 | 14 | $$ 15 | n\&(-n) 16 | $$ 17 | 18 | Ova operacija je vrlo bitna za Fenwickovo stablo. 19 | 20 | ### Struktura 21 | Svaki indeks $i$ u Fenwickovom stablu predstavlja sumu intervala $[i - 2^{\smash{j - 1}} + 1, i]$, gdje $j$ predstavlja posljednji postavljeni bit u indeksu $i$. Koristit ćemo 1-indeksiranje kod nizova. 22 | 23 | Primjer: 24 | Neka je $L = i - 2^{\smash{j - 1}} + 1$ početak intervala, a $R = i$ kraj intervala. 25 | 26 | | Indeks $i$ | bitovni prikaz | $j = i\&(-i)$ | $L$ | interval $[L, R]$ | 27 | |--------|--------------------|---------------|-------------------------|---------| 28 | | 1 | 0001 | 1 | $1 - 2^{\smash{0}} + 1$ |$[1, 1]$ | 29 | | 2 | 0010 | 2 | $2 - 2^{\smash{1}} + 1$ |$[1, 2]$ | 30 | | 3 | 0011 | 1 | $3 - 2^{\smash{0}} + 1$ |$[3, 3]$ | 31 | | 4 | 0100 | 3 | $4 - 2^{\smash{2}} + 1$ |$[1, 4]$ | 32 | | 5 | 0101 | 1 | $5 - 2^{\smash{0}} + 1$ |$[5, 5]$ | 33 | | 6 | 0110 | 2 | $6 - 2^{\smash{1}} + 1$ |$[5, 6]$ | 34 | | 7 | 0111 | 1 | $7 - 2^{\smash{0}} + 1$ |$[7, 7]$ | 35 | | 8 | 1000 | 4 | $8 - 2^{\smash{3}} + 1$ |$[1, 8]$ | 36 | 37 | Kako sad iz ovoga izračunati sumu prefiksa niza? 38 | Uzet ćemo indeks 7 kao primjer. On predstavlja sumu intervala $[7, 7]$. Ako od njega oduzmemo posljednji postavljeni bit (1), dobijemo indeks 6, tj. interval $[5, 6]$. Od broja 6 oduzmemo njemu posljednji postavljeni bit (2). Dobijemo indeks 4 ili interval $[1, 4]$. 39 | Imamo sljedeće intervale: $[1, 4]$, $[5, 6]$, $[7, 7]$. Njihovom unijom dobijemo interval $[1, 7]$ što predstavlja sumu prefiksa niza za indeks 7. 40 | 41 | Ilustracija: 42 | ![Fenwick tree - primjer](/img/fenwick-tree.png) 43 | 44 | Programski kod za računanje sume prefiksa koristeći Fenwickovo stablo: 45 | ```cpp 46 | // suma intervala [1, index] 47 | int prefixSum(int index, vector &fenwickTree) { 48 | int sum = 0; 49 | 50 | while(index >= 1) { 51 | sum += fenwickTree[index]; 52 | 53 | index -= index & -index; 54 | } 55 | 56 | return sum; 57 | } 58 | ``` 59 | 60 | ### Promjena vrijednosti elementa niza 61 | Dodavanje nekog broja $x$ elementu originalnog niza se vrši na sličan način kao i računanje sume prefiksa, samo ovaj put se kreće od manjeg indeksa prema većima. Kao primjer ćemo uzeti broj na indeksu 3, a cilj nam je promijeniti vrijednost za $x$ svim elementima Fenwickovog stabla koji prestavljaju sumu u kojoj se nalazi element na indeksu 3. Idemo obrnutim smjerom nego prije. 62 | 63 | Prvo ažuriramo Fenwickovo stablo na indeksu 3: $fenwickTree[3] += x$ 64 | Zatim indeksu dodajemo posljednji postavljeni bit (1) i dobijemo 4. 65 | Mijenjamo vrijednost Fenwickovog stabla na indeksu 4: $fenwickTree[4] += x$ 66 | Novi index: 8 67 | $fenwickTree[8] += x$ 68 | ... 69 | postupak se nastavlja dok indeks ne prijeđe granice niza. 70 | 71 | ![Fenwick tree - primjer](/img/fenwick-tree2.png) 72 | 73 | ### 74 | 75 | Programski kod je vrlo sličan onomu za dohvaćanje sume prefiksa: 76 | ```cpp 77 | //elementu na poziciji index dodaje x 78 | void update(int index, int x, vector &fenwickTree) { 79 | while(index < fenwickTree.size()) { 80 | fenwickTree[index] += x; 81 | 82 | index += index & -index; 83 | } 84 | } 85 | ``` 86 | 87 | :::caution Oprez 88 | U ovoj implementaciji $fenwickTree.size()$ ne označava pravu veličinu niza. 89 | Ona je zapravo za 1 manja zbog nultog indeksa kojeg tu ne koristimo. 90 | ::: 91 | 92 | Koristeći ovu funkciju možemo iz bilo kojeg niza izgraditi Fenwickovo stablo: 93 | ```cpp 94 | /* 95 | vraća Fenwickovo stablo za dani niz arr. 96 | pretpostavimo da se radi o 1-indeksiranju 97 | */ 98 | vector getFenwick(vector arr) { 99 | vector fenwickTree(arr.size(), 0); 100 | 101 | for(int i = 1; i < arr.size(); ++i) { 102 | update(i, arr[i], fenwickTree); 103 | } 104 | 105 | return fenwickTree; 106 | } 107 | ``` 108 | 109 | Sada efikasno možemo odgovoriti na upite o sumi nekog podniza od $l$ do $r$ na isti način kao kod niza [suma prefiksa](https://materijali.xfer.hr/docs/upiti-nad-intervalima-1/upiti-nad-statickim-poljima#suma-prefiksa): 110 | $$ 111 | rangeSum(l, r) = prefixSum(r) - prefixSum(l - 1) 112 | $$ 113 | :::info Informacija 114 | Korištenje 1-indeksiranja nam pokriva rubni slučaj kad se pozove $prefixSum$ za indeks 0. 115 | ::: 116 | 117 | Ovime smo pokrili promjene nad jednim elementom i upite nad intervalima. Pogledajmo sada kako obavljati promjene nad intervalima i upite nad jednim elementom. 118 | 119 | ### Range update, point query 120 | Kako bi ovo postigli, potrebno je podsjetiti se [difference arraya](https://materijali.xfer.hr/docs/upiti-nad-intervalima-1/difference-array). 121 | Kako bi intervalu $[l, r]$ promijenili vrijednost za $x$, potrebno je elementu na indeksu $l$ dodati $x$, a onom na indeksu $r + 1$ oduzeti. 122 | 123 | Dohvaćanje vrijednosti je također isto kao kod niza razlika, potrebno je napraviti sumu prefiksa. Prednost je u tome što se dohvaćanje elementa koristeći Fenwickovo stablo obavlja u $O(log(n))$. 124 | 125 | #### Programski kodovi 126 | 127 | ```cpp 128 | void rangeUpdate(int l, int r, int x, vector &fenwickTree) { 129 | update(l, x, fenwickTree); 130 | update(r + 1, -x, fenwickTree); 131 | 132 | /* 133 | nije potrebno provjeravati je li r + 1 izvan granica 134 | jer while(index < fenwickTree.size()) rješava taj slucaj 135 | */ 136 | } 137 | ``` 138 | 139 | ```cpp 140 | // vraca vrijednost elementa na indeksu i 141 | void pointQuery(int i, vector &fenwickTree) { 142 | return prefixSum(i, fenwickTree); 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /docs/upiti-nad-intervalima-1/sparse-table.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sparse table 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | 8 | 9 | ### Uvod 10 | 11 | Sparse table je struktura podataka koja nad statičkim nizovima može obaviti min/max upite nad intervalima u $O(1)$. Kako bismo to ostvarili, potrebno je nad nizom obaviti pretprocesiranje i izgraditi sparse table. Složenost pretprocesiranja je $O(n*log(n))$. Ovdje ćemo prikazati funkcionalnosti koristeći sparse table za min upite, a analogno se gradi i za max upite. 12 | Cilj nam je imati zapisano u matrici rješenja min upita za sve duljine koje su potencije broja 2. Dakle, imamo matricu $sparseTable[i][j]$, gdje $i$ predstavlja indeks početka intervala, a $j$ predstavlja eksponent pri potenciranju broja 2 što označava duljinu intervala. 13 | 14 | Primjer: 15 | $sparseTable[3][0]$ - minimalni broj na intervalu koji počinje na indexu 3, a duljine je $2^{\smash{0}} = 1$ 16 | $sparseTable[2][1]$ - početak intervala je 2, a on je duljine $2^{\smash{1}} = 2$ 17 | $sparseTable[1][2]$ - početak intervala je 1, a on je duljine $2^{\smash{2}} = 4$ 18 | 19 | Ako ovo prikažemo formulom: 20 | $$ 21 | sparseTable[i][j] = min(array[i], array[i + 1], ..., array[i + 2^{\smash{j}} - 1]) 22 | $$ 23 | 24 | 25 | 26 | ### Pretprocesiranje 27 | 28 | Prvi korak je upisati rješenja za sve duljine 1 (kad je $j = 0$). U tom slučaju samo prepišemo vrijednosti iz početnog niza: 29 | $$ 30 | sparseTable[i][0] = array[i]; 31 | $$ 32 | Imamo rješenja za intervale: $[0, 0], [1, 1], [2, 2], ...$ 33 | 34 | Sad računamo za intervale duljine 2 $([0, 1], [1, 2], [2, 3], ...)$. 35 | Primijetimo da se svaki taj interval može izračunati pomoću intervala duljine 1 koje već imamo zapisane. 36 | Minimalni broj na intervalu $[0, 1]$ je manji od $min[0, 0]$ i $min[1, 1]$. 37 | Nadalje, $min[1, 2] = min(min[1, 1], min[2, 2])$, i tako za svaki element niza (osim zadnjeg, jer iz njega ne može početi interval duljine 2). 38 | 39 | Kako to prikazati pomoću naše matrice? 40 | $$ 41 | sparseTable[0][1] = min(sparseTable[0][0], sparseTable[1][0]) 42 | $$ 43 | $$ 44 | sparseTable[1][1] = min(sparseTable[1][0], sparseTable[2][0]) 45 | $$ 46 | $$ 47 | sparseTable[2][1] = min(sparseTable[2][0], sparseTable[3][0]) 48 | $$ 49 | $$ 50 | sparseTable[3][1] = min(sparseTable[3][0], sparseTable[4][0]) 51 | $$ 52 | $$ 53 | ... 54 | $$ 55 | 56 | Nastavljamo s intervalima duljine 4 $(j = 2)$. Primijetimo da se interval $[0, 3]$ može prikazati kao unija intervala $[0, 1]$ i $[2, 3]$, a podatak o najmanjem broju za ta dva intervala smo već izračunali. 57 | Vrijedi: 58 | $$ 59 | sparseTable[0][2] = min(sparseTable[0][1], sparseTable[2][1]) 60 | $$ 61 | $$ 62 | sparseTable[1][2] = min(sparseTable[1][1], sparseTable[3][2]) 63 | $$ 64 | $$ 65 | ... 66 | $$ 67 | 68 | Postupak se nastavlja za sve $j$ dok je duljina intervala $2^{\smash{j}}$ manja ili jednaka duljini niza. 69 | 70 | ### Primjer pretprocesiranja 71 | 72 | Pogledajmo sljedeći primjer: 73 | Imamo niz duljine $N = 8$, $[1, 3, 5, 8, 6, 1, 4, 2]$ i iz njega je potrebno napraviti sparse table. Ispod se nalazi slika kako bi lakše vizualizirali postupak. 74 | 75 | ![Sparse table - primjer](/img/sparseTable.png) 76 | 77 | Prvo ispunjavamo sparseTable za $j = 0$ $(duljina\_intervala = 2^{\smash{0}} = 1)$ - žuti redak sa slike. Kao što je prije rečeno, samo prepišemo vrijednosti iz niza. 78 | 79 | Nakon toga, za $j = 1$ $(duljina\_intervala = 2^{\smash{1}} = 2)$ - plavi dio sa slike, računamo $sparseTable[i][1]$ gdje vrijedi $0 \le i \lt N - 1$ koristeći podatke iz prošlog koraka. Npr. za $sparseTable[3][1]$ (interval $[3, 4]$) uzet ćemo $sparseTable[3][0] = 8$ (interval $[3, 3]$) i $sparseTable[4][0] = 6$ (interval $[4, 4]$), a manji od ta dva broja zapisujemo kao rezultat. 80 | 81 | Nastavljamo s $j = 2 (duljina\_intervala = 2^{\smash{2}} = 4)$. Opet računamo rješenje koristeći rezultate iz prošlog koraka. Na sljedećim slikama je prikazano koja polja se koriste prilikom računanja rezultata. 82 | 83 | ![Sparse table - primjer](/img/sparseTable2.png) 84 | $$ 85 | sparseTable[0][2] = min(sparseTable[0][1], sparseTable[2][1]) 86 | $$ 87 | 88 |   89 |   90 | 91 | ![Sparse table - primjer](/img/sparseTable3.png) 92 | $$ 93 | sparseTable[1][2] = min(sparseTable[1][1], sparseTable[3][1]) 94 | $$ 95 | 96 | Postupak se nastavlja dok je $i + 2^{\smash{j}} - 1 < N$, tj. dok interval ne izlazi iz granica niza. 97 | 98 |   99 |   100 | 101 | ![Sparse table - primjer](/img/sparseTable4.png) 102 | $$ 103 | sparseTable[0][3] = min(sparseTable[0][2], sparseTable[4][2]) 104 | $$ 105 | 106 | Ovime smo završili pretprocesiranje i izgradili sparse table. 107 | Formula: 108 | $$ 109 | sparseTable[i][j] = min(sparseTable[i][j-1], sparseTable[i + 2^{\smash{j-1}}][j-1]) 110 | $$ 111 | Zašto baš $i + 2^{\smash{j-1}}$? Jer nam treba desna polovica intervala duljine $2^{\smash{j}}$, dakle početnom indeksu pridodajemo pola duljine niza. 112 | 113 | #### Programski kod 114 | ```cpp 115 | vector < vector > createSparseTable(vector arr) { 116 | int N = arr.size(); 117 | int K = log2(N); //2^K je najveca duljina intervala koja ne prelazi duljinu niza 118 | 119 | vector row(K + 1); 120 | vector < vector > sparseTable(N, row); 121 | 122 | for(int i = 0; i < N; i++) { // j = 0 123 | sparseTable[i][0] = arr[i]; //intervali duljine 2^0 = 1 124 | } 125 | 126 | for(int j = 1; j <= K; ++j) { 127 | int len = pow(2, j); //duljina intervala 128 | 129 | for(int i = 0; i + len - 1 < N; ++i) { //uvjet je da interval [i, i + 2^j - 1] ne prelazi izvan granica niza 130 | sparseTable[i][j] = min(sparseTable[i][j - 1], sparseTable[i + len/2][j - 1]); 131 | } 132 | } 133 | 134 | 135 | return sparseTable; 136 | } 137 | ``` 138 | 139 | :::tip Napomena 140 | Često se umjesto $pow(2, n)$ koristi logički posmak ulijevo (engl. *bitshift*) za potencije broja 2. $(1 << n)$ 141 | ::: 142 | 143 | ### Upiti 144 | 145 | Kako sada odgovoriti na upit za najmanji broj nad nekim intervalom? Uzmemo najveću potenciju broja 2, a da ne prelazi duljinu intervala. Na primjer, tražimo rješenje za interval $[2, 7]$. Najveća potencija broja 2 u ovom slučaju je 4. 146 | Vrijedi $j = 2$ i sad radimo upit za $sparseTable[2][j]$ (početak intervala je 2, a duljina $2^{\smash{j}} = 4$). Osim toga uzimamo upit i za desni dio intervala, točnije $[4, 7]$, a to je $sparseTable[7 - 2^{\smash{j}} + 1][j]$. Uzmemo manju vrijednost i ona je rješenje. 147 | Možemo vidjeti da ta dva upita uistinu pokrivaju cijeli interval. 148 | ![Sparse table - primjer](/img/sparseTable5.png) 149 | 150 | Konačno, formula za dohvat najmanjeg broja na intervalu $[L, R]$ (duljina intervala = R - L + 1): 151 | 152 | $$ 153 | min(sparseTable[L][j], sparseTable[R - 2^{\smash{j}} + 1][j]) 154 | $$ 155 | 156 | $$ 157 | j = floor(log2(R - L + 1)) 158 | $$ 159 | -------------------------------------------------------------------------------- /docs/dinamicko-programiranje/sto-je-dinamicko-programiranje.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Što je dinamika? 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | ### Uvod 12 | 13 | Prvo pogledajmo jedan zadatak. Zadana je matrica veličine $N*N$ gdje je $N <= 1000$ te su u polja upisani prirodni brojevi $x_{i,j} < 1e9$. Želimo naći put iz gornjeg lijevog polja do donjeg desnog polja, krećući se samo dolje i desno, čija je suma ćelija na putu najveća. 14 | 15 | Možemo primijetiti da je iz jednog polja moguće otići samo u dva druga polja pa bi netko mogao pomisliti da odlaskom u polje s većim brojem dolazimo do rješenja. Ova ideja mogla bi se nazvati pohlepnim pristupom, no ona nam neće dati točno rješenje, a dokaz ostavljamo čitatelju za vježbu(HINT: nađite protuprimjer). 16 | 17 | Sljedeće rješenje koje se prirodno nameće je isprobati sve puteve te uzeti onaj s najvećom vrijednošću. Možemo primijetiti da će put uvijek biti duljine $2 * (N - 1)$ jer sigurno moramo otići $N - 1$ puta desno i $N - 1$ puta dolje. Postoji $2 * (N - 1) \choose (N - 1)$ različitih puteva, dokaz ove tvrdnje također ostavljamo čitatelju za vježbu. Očito će, za $N = 1000$, prolazak svim putevima biti prespor za uobičajeno vremensko ograničenje od jedne sekunde. 18 | 19 | ### Što je dinamika 20 | 21 | Primijetimo sljedeće: ako se nalazimo u ćeliji $X, Y$ matrice $M$ onda će maksimalna suma koju možemo dobiti do te ćelije biti $max(rješenje[X - 1, Y], rješenje[X, Y - 1]) + M[X, Y]$ što znači da ako znamo rezultate ćelije iznad i lijevo od trenutne ćelije onda jednostavno možemo izračunati rezultat za trenutnu ćeliju. 22 | 23 | Upravo je to ideja dinamičkog programiranja. Dinamičko programiranje karakterizira skup stanja i jedna(ili više) funkcija prijelaza. Uvijek je zadano početno stanje i pomoću prijelaza potrebno je odrediti vrijednost traženog stanja. Specifično za ovaj zadatak, postavimo da nam je stanje optimalna suma u nekoj ćeliji, početno stanje je ćelija $(0,0)$ čija je početna vrijednost broj zapisan u toj ćeliji, a stanje čiju vrijednost tražimo je $(N - 1, N - 1)$. Funkcija prijelaza je navedena gore. 24 | 25 | ### Složenost dinamike 26 | 27 | Kod dinamičkog programiranje gotovo je uvijek potrebno posjetiti sva stanja. U svako stanje potrebno je doći iz nekog prethodnog stanja pomoću funkcije prijelaza te prema tome možemo zaključiti da je složenost takvog programa jednaka broju stanja pomnoženog sa složenošću prijelaza. Za naš zadatak složenost bi bila $O(N^2)$ jer postoji $N^2$ stanja, a složenost prijelaza je $O(1).$ 28 | 29 | ### Rješenje 30 | 31 | ```cpp 32 | int solve(int x, int y){ 33 | //nalazimo se u početnom stanju 34 | if(x == 0 && y == 0) return M[x][y]; 35 | //nalazimo se u gornje retku 36 | if(x == 0) return M[x][y] + solve(x, y - 1); 37 | //nalazimo se u prvom stupcu 38 | if(y == 0) return M[x][y] + solve(x - 1, y); 39 | //nalazimo se u općem polju matrice 40 | return max(solve(x - 1, y), solve(x, y - 1)) + M[x][y]; 41 | } 42 | 43 | int main(){ 44 | // unos podataka 45 | cout << solve(n - 1, n - 1); 46 | return 0; 47 | } 48 | ``` 49 | Ovo rješenje je skica točnog rješenja, no ono nam može javiti dvije greške, prije nego nastavite čitati razmislite koje bi to greške mogle biti. 50 | 51 | ### Memoizacija 52 | 53 | Jedna od grešaka u skici rješenja je to što više puta posjećujemo ista stanja i za njih više puta računamo njihovu vrijednost. Npr. kada iz zadnjeg stanja provjerimo stanje od polja iznad$(X - 1, Y)$ tada ćemo iz toga polja rekurzivno pozvati polje lijevo od njega$(X - 1, Y - 1)$, također kada iz zadnjeg stanja provjerimo stanje od polja lijevo$(X, Y - 1)$ iz tog polje ćemo također rekurzivno posjetiti polje iznad njega $(X - 1, Y - 1)$. Možemo primijetiti da smo dva puta pozvali izračun vrijednosti u polju $(X - 1, Y - 1)$. To je nepotrebno jer će svaki izračun dati isti rezultat, a osim toga što je nepotrebno također će izazvati **TLE**(razmislite zašto). 54 | 55 | Za ovaj problem postoji vrlo jednostavno rješenje. Memoizacija. 56 | 57 | Memoizacija je postupak pamćenja posjećenih stanja kako ih ne bi bespotrebno računali više puta. Za naš zadatak stvorit ćemo matricu koju ćemo popuniti sa $-1$ te će taj broj predstavljati neposjećeno stanje, a kad izračunamo neko stanje onda ćemo u matricu zapisati izračunatu vrijednost. Ako u toj matrici na nekom mjestu broj nije $-1$ tada znamo da smo to stanje već izračunali. Primjenom memoizacije naše rješenje izgleda ovako. 58 | 59 | ```cpp 60 | //najveća moguća dimenzija matrice 61 | const int MAXN = 1000; 62 | 63 | int n; 64 | int M[MAXN][MAXN]; 65 | int memo[MAXN][MAXN]; 66 | 67 | int solve(int x, int y){ 68 | //ako na trenutnom mjestu postoji broj koji nije -1 tada je to stanje već posjećeno 69 | if(memo[x][y] != -1) return memo[x][y] 70 | //nalazimo se u početnom stanju 71 | if(x == 0 && y == 0) return M[x][y]; 72 | //nalazimo se u gornje retku 73 | if(x == 0) return memo[x][y] = M[x][y] + solve(x, y - 1); 74 | //nalazimo se u prvom stupcu 75 | if(y == 0) return memo[x][y] = M[x][y] + solve(x - 1, y); 76 | //nalazimo se u općem polju matrice 77 | return memo[x][y] = max(solve(x - 1, y), solve(x, y - 1)) + M[x][y]; 78 | } 79 | 80 | int main(){ 81 | //popunjavamo memo sa -1 82 | memset(memo, -1, sizeof memo); 83 | cin >> n; 84 | for(int i = 0; i < n; i++) 85 | for(int j = 0; j < n; j++) 86 | cin >> M[i][j]; 87 | cout << solve(n - 1, n - 1); 88 | return 0; 89 | } 90 | ``` 91 | Prikazano rješenje jako je blizu ispravnog rješenja, no i dalje postoji jedan detalj koji će nam stvarati probleme. Što ako se u svakom polju matrice nalazi broj $1e9$, tada će naše rješenje biti $1998 * 1e9$, a to je broj koji izlazi iz granica $int$-a što znači da u našem rješenju trebamo koristiti *long long int*. Time će naše rješenje biti točno. 92 | 93 | ### Digresija 94 | 95 | Zadatke s dinamičkim programiranjem uglavnom je moguće riješiti rekurzivno i iterativno. Svaki pristup ima svoje prednosti i mane te jedino vježbom možete vidjeti kako vam je lakše i brže rješavati takve zadatke. Kod rekurzivnog rješavanja često je potrebno paziti na to da se funkcija ne zove više puta za isto stanje(nekada to nije moguće samo memoizacijom), no rekurzivni kodovi uglavnom budu kraći i lakše čitljiv. S druge strane kod iterativnog obilaska ponekad nije intuitivno kako obići sva stanja. Odabir načina pisanja stvar je osobne preferencije, a usporedbe radi napisat ćemo i iterativno rješenje zadatka iz Uvoda. 96 | 97 | ```cpp 98 | const int MAXN = 1000; 99 | int n; 100 | int M[MAXN][MAXN]; 101 | long long int memo[MAXN][MAXN]; 102 | 103 | int main(){ 104 | cin >> n; 105 | for(int i = 0; i < n; i++) 106 | for(int j = 0; j < n; j++) 107 | cin >> M[i][j]; 108 | for(int i = 0; i < n; i++){ 109 | for(int j = 0; j < n; j++){ 110 | if(i == 0 && j == 0) memo[i][j] = M[i][j]; 111 | else if(i == 0) memo[i][j] = M[i][j] + memo[i][j - 1]; 112 | else if(j == 0) memo[i][j] = M[i][j] + memo[i - 1][j]; 113 | else memo[i][j] = M[i][j] + max(memo[i - 1][j], memo[i][j - 1]); 114 | } 115 | } 116 | cout << memo[n - 1][n - 1]; 117 | return 0; 118 | } 119 | ``` -------------------------------------------------------------------------------- /docs/sortiranje-i-pretrazivanje/sortiranje.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sortiranje 3 | --- 4 | 5 | import Author from '@site/src/react_components/author.js'; 6 | 7 | import Spoiler from '@site/src/react_components/spoiler.js'; 8 | 9 | 10 | 11 | Algoritme za sortiranje koristimo kako bismo složili podatke u smisleni poredak prema nekom kriteriju. Iako ćemo ovdje prvenstveno govoriti o primjeni sortiranja u natjecateljskom programiranju (sortiranje nad brojevima, stringovima...), treba biti svjestan da je primjena puno šira pa je ova vještina potrebna svakome tko se želi ozbiljnije baviti programiranjem. Također, sortirasnje je ključan preduvjet za mnoge druge korisne algoritme. U ovom ćete članku naučiti nešto o različitim sortovima i njihovoj složenosti. Ako vas zanima više, istražite dostupne linkove ili se javite putem foruma 😄. 12 | 13 | ## $O(n^2)$ algoritmi 14 | 15 | Najjednostavniji algoritmi sortiraju liste u kvadratnoj složenosti. Jedan od najpoznatijih primjera ovakvog sortiranja je tzv. **bubble sort**. Algoritam se sastoji od $n$ koraka. U svakom koraku prolazimo kroz sve elemente u listi koju sortiramo i uspoređujemo susjedne članove. Ako dva susjedna člana nisu u odgovarajućem poretku (npr. sortiramo uzlazno), algoritam im mijenja mjesta. Tako osiguravamo da će se nakon prvog prolaska kroz niz najveći član nalaziti na točnom mjestu. Nakon maksimalno $n$ koraka svi će članovi biti na svojim mjestima i lista će biti sortirana. Više o bubble sortu pročitajte [ovdje](https://www.tutorialspoint.com/data_structures_algorithms/bubble_sort_algorithm.htm "Bubble sort"). 16 | 17 | ```cpp 18 | for(int i=0; i array[j+1]) { 21 | swap(array[j], array[j+1]); 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | Prednost bubble sorta i sličnih algoritama je što su jako kratki za kodiranje i lako se razumiju. Ipak, u natjecateljskom programiranju češće ćete sretati veće količine podataka za koje je kvadratna složenost prevelika (npr. za $n=10^5$ kvadratna složenost daje vrijeme izvršavanja od oko $100$ sekundi što ne prolazi time limit$^1$. Sada se postavlja pitanje kako ubrzati ovaj algoritam? Početna ideja mogla bi biti prekinuti izvršavanje u unutarnjoj petlji ako nismo napravili niti jednu zamjenu. To bi ponešto optimiziralo program, ali složenost je u najgorem slučaju i dalje $O(n^2)$. Može li brže? Nego što! 28 | 29 | $^1$ više o time limitu pročitajte ovdje. 30 | 31 | 32 | 33 | ## $O(n \log(n))$ algoritmi 34 | 35 | Postoji više algoritama koji rade u ovoj složenosti, ali njihovi detalji nisu toliko bitni za natjecateljsko programiranje pa ćemo ih ovdje samo spomenuti. Više o njima možete pročitati 36 | na dostupnim linkovima. 37 | 38 | - **merge sort** - sort koji se bazira na rekurziji, dijeli početnu listu na manje dijelove i sortira svaki zasebno, a potom ih spaja prilikom povratka u rekurziji. Više pročitajte [ovdje](https://www.geeksforgeeks.org/merge-sort/ "Merge sort"). 39 | - **heap sort** - sort koji radi nad strukturom poznatom kao 'binary heap', sličan selection sortu. Detalji [ovdje](https://www.geeksforgeeks.org/heap-sort/ "Heap sort"). 40 | - **quick sort** - izabire referentni element (pivot), a ostale raspodjeljuje u odnosu na njega. Postoji više različitih varijanti quick sorta, a razlikuju se u načinu izbora referentnog elementa. Detalji [ovdje](https://www.geeksforgeeks.org/quick-sort/ "Quick sort"). 41 | 42 | :::tipsavjet 43 | Prije nego počnete pisati kod, dobro razmislite o složenosti programa kojeg ste smislili. Pokušajte uvijek tražiti rješenje koje prolazi ograničenja, a zahtijeva minimalno vrijeme pisanja. 44 | ::: 45 | 46 | ## Može li još brže? 47 | 48 | Nažalost, može se pokazati da za algoritme koji uspoređuju elemente niza nije moguće postići manju složenost od $O(n \log(n))$. Ipak, postoje algoritmi koji rade brže, ali pritom **ne uspoređuju članove niza**. Primjer je **counting sort** koji radi u linearnoj složenosti. Ovaj se algoritam temelji na tome da unaprijed imamo neku informaciju o članovima liste koju sortiramo. Npr. možemo zamisliti da je potrebno sortirati $10^6$ brojeva, ali su svi ti brojevi u intervalu $[0,100]$. Counting sort napravi praznu pomoćnu listu ispunjenu nulama. Potom jednom prolazimo kroz sve članove u listi koju sortiramo te na $i$-toj poziciji u pomoćnoj listi pratimo koliko se puta pojavio broj iznosa $i$. Pogledajmo konkretan primjer. Neka je potrebno sortirati niz brojeva $[2, 44, 23, 25, 88, 44, 23]$. Nakon što provedemo sortiranje na poziciji $i=44$ u pomoćnoj listi piše $2$ zato što se broj $44$ nalazi dva puta u nizu koji sortiramo. Po završetku sortiranja prolazimo kroz pomoćnu listu tako da za svaku poziciju i ispisujemo onoliko brojeva kolika je vrijednost na toj poziciji. 49 | 50 | ```cpp 51 | int lista[101]; //na početku nule 52 | for(int i=0; i> x; //brojevi koje unosimo 54 | lista[x]++; 55 | } 56 | 57 | //ispis sortiranog niza 58 | for(int i=0; i<=100; i++) { 59 | for(int j=0; jgotove implementacije sorta. Pogledajmo primjer sortiranja nekoliko tipova spremnika u CPP-u: 69 | 70 | ```cpp 71 | vector v = {4,2,5,3,8,5,8,3}; 72 | sort(v.begin(), v.end()); 73 | 74 | int n = 7; 75 | int a[] = {4,2,5,3,8,5,8,3}; 76 | sort(a,a+n); 77 | 78 | string s="sladoled"; 79 | sort(s.begin(), s.end()); //addellos 80 | ``` 81 | 82 | ### Komparator 83 | 84 | Kao treći argument funkciji _sort_ moguće je zadati operator usporedbe (komparator). On mora biti definiran nad tipom podataka koji sortiramo (npr. nad parovima cijelih brojeva). 85 | C++ ima već ugrađeni komparator za ovaj tip pa se po defaultu parovi sortiraju tako da se prvo uspoređuje prvi element iz para, a potom drugi. Što ako želimo drugačiji kriterij? 86 | Tu nalazimo primjenu **vanjskih komparatora** (custom comparators). Npr. zamislimo da parove integera želim sortirati prema drugom elementu iz para. 87 | 88 | ```cpp 89 | #include 90 | #define pii pair 91 | using namespace std; 92 | 93 | bool comp(pii a, pii b) { 94 | if (a.second == b.second) { 95 | return a.first < b.first; 96 | } 97 | return a.second < b.second; 98 | } 99 | 100 | int main() { 101 | vector> v; //... dodavanje parova u vektor 102 | 103 | sort(v.begin(), v.end(),comp); 104 | return 0; 105 | } 106 | ``` 107 | 108 | ### Reverse funkcija 109 | 110 | Reverse funkcija okreće poredak elemenata u bilo kojem tipu spremnika (lista, vektor...). Okreće elemente kojima su pozicije u intervalu \[first,last> 111 | i radi u složenosti $O(n)$. 112 | 113 | ```cpp 114 | vector v; //... dodavanje elemenata u vektor 115 | 116 | reverse(v.begin()+1, v.begin()+5); 117 | //input: 1,2,3,4,5,6,7,8 118 | //output: 1,5,4,3,2,6,7,8 119 | 120 | reverse(v.begin(), v.end()); 121 | //input: 1,2,3,4,5,6,7,8 122 | //output: 8,7,6,5,4,3,2,1 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/doprinos-ovim-materijalima/upute-za-markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upute za Markdown 3 | --- 4 | 5 | import Spoiler from '@site/src/react_components/spoiler.js'; 6 | 7 | Možete pisati članke koristeći [GitHub-flavored Markdown sintaksu](https://github.github.com/gfm/) 8 | 9 | 10 | ## Markdown sintaksa 11 | 12 | Ovdje možete pronaći primjere markdown stilova s kojima možete uljepšati svoje članke. 13 | 14 | ## Naslovi 15 | 16 | # H1 - Napišite najbolji članak 17 | 18 | ## H2 - Napišite najbolji članak 19 | 20 | ### H3 - Napišite najbolji članak 21 | 22 | #### H4 - Napišite najbolji članak 23 | 24 | ##### H5 - Napišite najbolji članak 25 | 26 | ###### H6 - Napišite najbolji članak 27 | 28 | --- 29 | 30 | ## Naglašavanje 31 | 32 | Naglašavanje, ili italik, radite koristeći *zvjezdice* ili _podvlake_. 33 | 34 | Jako naglašavanje, ili bold, radite koristeći **dvije zvjezdice** ili __dvije podvlake__. 35 | 36 | Mješovito naglašavanje radite koristeći **zvjezdice i _podvlake_**. 37 | 38 | Precrtani tekst radite koristeći dvije tilde. ~~Evo ovako.~~ 39 | 40 | --- 41 | 42 | ## Liste 43 | 44 | 1. Prva stavka numerirane liste 45 | 1. Još jedna stavka 46 | - Nenumerirana podlista. 47 | 1. Nije bitno koji broj navedete, glavno je da je broj 48 | 1. Numerirana podlista 49 | 1. Još jedna stavka. 50 | 51 | * Nenumerirane liste mogu koristiti zvjezdice 52 | 53 | - ili crtice 54 | 55 | + ili pluseve. 56 | 57 | --- 58 | 59 | ## Veze 60 | 61 | [Ovo je veza u jednom redu](https://www.google.com/) 62 | 63 | [Ovo je veza u jednom redu s naslovom](https://www.google.com/ "Naslovnica Googlea") 64 | 65 | [Ovo je veza s referencom][neki tekst kojim označavate link] 66 | 67 | [Možete koristiti i brojeve za linkove s referencom][1] 68 | 69 | Ili ostaviti prve zagrade praznima i koristiti [sami tekst veze]. 70 | 71 | URL-ovi i URL-ovi u šiljastim zagradama će se automatski pretvoriti u linkove. http://www.example.com/ ili \ a nekad i example.com (ne na GitHubu, na primjer). 72 | 73 | Ovaj tekst služi samo da pokažemo da nakon njega mogu slijediti veze referenci. 74 | 75 | [neki tekst kojim označavate link]: https://www.mozilla.org/ 76 | [1]: http://slashdot.org/ 77 | [sami tekst veze]: http://www.reddit.com/ 78 | 79 | --- 80 | 81 | ## Slike 82 | 83 | Prikaz slika (prijeđite mišem preko slike za alt-tekst): 84 | 85 | Veza u jednom redu: ![alternativni tekst](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo tekst 1') 86 | 87 | Veza s referencom: ![alt text][logo] 88 | 89 | [logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo tekst 2' 90 | 91 | Možete koristiti i veze na mape u ovom projektu (slike spremajte u mapu `static/img`). 92 | 93 | ![img](../../static/img/logo.svg) 94 | 95 | --- 96 | 97 | ## Programski kod 98 | 99 | ```cpp 100 | #include 101 | 102 | using namespace std; 103 | 104 | int main(void){ 105 | cout << "Označavanje C++ sintakse."; 106 | 107 | return 0; 108 | } 109 | ``` 110 | 111 | ```python 112 | s = "Označavanje Python sintakse" 113 | print(s) 114 | ``` 115 | 116 | ``` 117 | Nije označen jezik, pa nema ni označavanja sintakse. 118 | Možemo dodati oznaku. 119 | ``` 120 | 121 | ```js {2} 122 | function naglasiMe() { 123 | console.log('Možete i posebno naglasiti neke retke!'); 124 | } 125 | ``` 126 | 127 | --- 128 | 129 | ## Tablice 130 | 131 | Možete koristiti dvotočke da biste namjestili stupce. 132 | 133 | | Tablice | Su | Kul | 134 | | ------------- | :-----------: | -----: | 135 | | stupac 3 je | pomaknut udesno | $1600 | 136 | | stupac 2 je | centriran | $12 | 137 | | zebre su | super | $1 | 138 | 139 | Barem 3 crtice moraju dijeliti naslov od ostatka tablice. Vanjske uspravne crte (|) nisu obvezne, te nije potrebno uredno posložiti sve retke i stupce u Markdown obliku. Možete koristiti i Markdown u ćelijama tablica. 140 | 141 | | Markdown | se | svejedno | 142 | | -------- | --------- | ------------- | 143 | | _skroz_ | `dobro` | **prikazuje** | 144 | | 1 | 2 | 3 | 145 | 146 | --- 147 | 148 | ## Blok citati 149 | 150 | > Blok citati su jako zgodni u emailovima za prikazivanje teksta odgovora. 151 | 152 | Prekid citata. 153 | 154 | > Ovo je jako dug citat koji će se svejedno uredno prikazati i kad prijeđe u novi redak. Nastavimo pisati da bismo bili sigurni da je dovoljno dug da se prebaci u novi redak na svim uređajima. E da, možete _dodati_ **Markdown** u blok citate. 155 | 156 | --- 157 | 158 | ## HTML 159 | 160 |
161 |
Lista definicija
162 |
Je nešto što ljudi nekad koriste
163 | 164 |
Markdown u HTML-u
165 |
*Ne* radi baš **najbolje**. Koristite HTML oznake.
166 |
167 | 168 | --- 169 | 170 | ## Matematički izrazi 171 | 172 | Matematičke izraze upisujemo koristeći Latex. Na ovoj [stranici](https://www.overleaf.com/learn/latex/mathematical_expressions) možete pronaći znakove koji će vam možda zatrebati pri pisanju matematičkih izraza. 173 | 174 | Da biste započeli matematički izraz u istoj liniji, koristi se znak `$`, pa izraz, pa opet `$`, npr. `$F_{n} = F_{n-1} + F_{n-2}$`, što će prikazati izraz $F_{n} = F_{n-1} + F_{n-2}$. Da biste započeli blok izraz, koristite dva znaka `$` na početku i na kraju bloka, npr.: 175 | 176 | ``` 177 | $$ 178 | a^{2} + b^{2} = c^{2} 179 | $$ 180 | ``` 181 | 182 | A prikazat će se: 183 | 184 | $$ 185 | a^{2} + b^{2} = c^{2} 186 | $$ 187 | 188 | --- 189 | 190 | ## Prekidi redaka 191 | 192 | Započet ćemo s ovim retkom. 193 | 194 | Ovaj redak je odvojen od prošlog s dva prijelaza u novi red, pa će postati _odvojeni odlomak_. 195 | 196 | Ovaj redak je također odvojeni odlomak, ali... 197 | Ovaj redak je odvojen samo jednim prijelazom u novi red, pa će ostati u _istom odlomku_. 198 | 199 | --- 200 | 201 | ## Skriveni tekst 202 | 203 | Želite li napisati tekst koji neće odmah biti vidljiv čitatelju, koristite React komponentu Spoiler: 204 | 205 | ``` 206 | 207 | ``` 208 | 209 | što će se prikazati ovako: - klikom na sivi pravokutnik otkrivate skriveni tekst, ponovnim klikom ga skrivate. 210 | 211 | Želite li prikazati veći komad teksta, preporučujemo korištenje HTML oznake *details*: 212 | 213 | ``` 214 |
215 | 216 | Ovo je naslov skrivenog teksta. 217 | 218 |
219 | A ovo je skriveni tekst. 220 |
221 |
222 | ``` 223 | 224 |
225 | 226 | Ovo je naslov skrivenog teksta. 227 | 228 |
229 | A ovo je skriveni dio gdje možete ubaciti puno više teksta. Nastavit ću s tekstom kako bi se na svim ekranima prikazao u barem dva retka. Nažalost, matematički izrazi trenutno ne rade unutar HTML-a, pokušat ćemo to popraviti uskoro. 230 |
231 |
232 | 233 | 234 | ## Napomene 235 | 236 | :::noteprimijetite 237 | 238 | Ova napomena vam okreće pozornost na nešto. 239 | 240 | ::: 241 | 242 | :::tipsavjet 243 | 244 | Ova napomena vam daje savjet. 245 | 246 | ::: 247 | 248 | :::importantbitno 249 | 250 | Ova napomena ukazuje na nešto bitno. 251 | 252 | ::: 253 | 254 | :::cautionoprez 255 | 256 | Ova napomena vam govori da budete oprezni. 257 | 258 | ::: 259 | 260 | :::warningupozorenje 261 | 262 | Ova napomena vas upozorava na nešto. 263 | 264 | ::: 265 | --------------------------------------------------------------------------------