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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | 
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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
84 | $$
85 | sparseTable[0][2] = min(sparseTable[0][1], sparseTable[2][1])
86 | $$
87 |
88 |
89 |
90 |
91 | 
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 | 
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 | 
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: 
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 | 
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 |
--------------------------------------------------------------------------------