Lectia10.Probleme OJI 2017
Lectia10.Probleme OJI 2017
– Probleme concurs
Problema Șir
• Dificultate: 3
• Autor: Rodica Pintea
• Online: InfoArena, PbInfo
Rezumat
Considerăm șirurile de lungime n, care încep cu 1, cu proprietatea că fiecare
element este mai mare decât predecesorul lui cu cel mult 1. Să se determine numărul
de șiruri ce se termină în u, și respectiv numărul de șiruri în care un element apare
de cel mult r ori.
Soluție
𝑢−1
Răspunsul pentru prima cerință este 𝐶𝑛−1 , iar explicația este exact cea de la
numărul de partiții ordonate ale unui număr natural, din acest articol. Șirul nostru
este partiționat în u secvențe, iar noi trebuie să alegem capetele acestora. Cum
ultima secvență are capătul fixat în poziția n, rămâne să alegem capetele doar pentru
primele u−1 secvențe. Acestea trebuie să ia valori distincte din
mulțimea {1,2,…,n−1}, așa că numărul lor este 𝐶𝑛−1 𝑢−1
. Având nevoie de o singură
combinare, o vom calcula folosind invers modular, pe baza formulei:
𝑢−1
𝐶𝑛−1 =(n−1)!⋅(u−1)!−1⋅(n−u)!−1
La a doua cerință vom folosi programare dinamică astfel: Notăm cu dp[i] numărul
de șiruri de lungime i cu proprietatea dată. Putem obține un șir de
lungime i adăugând j valori egale la un șir de lungime i−j, unde 1≤j≤r. Valorile
adăugate sunt unic determinate de ultimul element al șirului de lungime i−j. Adică,
dacă acesta se termină în x, atunci șirul nou se va termina în j de x+1. Deci,
recurența este:
dp[i]=dp[i−1]+dp[i−2]+⋯+dp[i−r]
Dinamica poate fi calculată imediat în O(n⋅r), însă poate fi optimizată foarte ușor
făcând următoarea observație. Scriem una sub alta recurențele pentru
dp[i] și dp[i−1]:
Lectia nr. 10. – Probleme concurs
dp[i] =dp[i−1]+dp[i−2]+⋯+dp[i−r]
dp[i−1]=dp[i−2]+dp[i−3]+⋯+dp[i−r−1]
Dacă scădem cele două relații, se vor reduce o grămadă de termeni și vom obține:
dp[i]−dp[i−1]=dp[i−1]−dp[i−r−1]
De unde:
dp[i]=2⋅dp[i−1]−dp[i−r−1]
Sursă C++
Problema Șir
#include <bits/stdc++.h>
using namespace std;
ifstream fin("sir9.in");
ofstream fout("sir9.out");
int p, n, u;
int dp[NMAX];
int modInv(int n) {
return pwr(n, MOD - 2);
}
int fact(int n) {
int f = 1;
for (int i = 2; i <= n; i++)
f = 1LL * f * i % MOD;
return f;
}
Lectia nr. 10. – Probleme concurs
int main() {
fin >> p >> n >> u;
if (p == 1)
fout << 1LL * fact(n - 1) * modInv(fact(u - 1)) % MOD
* modInv(fact(n - u)) % MOD << '\n';
else {
dp[0] = dp[1] = 1;
for (int i = 2; i <= n; i++)
dp[i] = (2 * dp[i - 1] - (i >= u - 1 ? dp[i - u -
1] : 0) + MOD) % MOD;
fout << dp[n] << '\n';
}
return 0;
}
Problema Rover
• Dificultate: 3
• Autor: Mircea Lupșe-Turpan
• Online: InfoArena, PbInfo
Rezumat
Avem un rover care se poate deplasa (cu o celulă în nord, sud, est sau vest)
într-o matrice pătratică cu n linii și n coloane. Fiecare celulă (zonă) a
matricei are o stabilitate reprezentată printr-un număr natural, iar rover-ul
are greutatea g. O zonă cu stabilitatea mai mică decât g este considerată o
zonă periculoasă pentru rover.
Soluție: Cerința 1
Pentru prima cerință putem defini costul unei celule drept numărul minim de
zone periculoase pe care rover-ul trebuie să le parcurgă pentru a ajunge la ea.
Se observă ușor că atunci când trecem din celulaA în celula B, avem relația
de recurență cost(B)=cost(A)+zonaSigura(B). Evident, zonaSigura(B)
Lectia nr. 10. – Probleme concurs
este 1 dacă B este o zonă sigură, și 0 dacă nu. Această recurență ne indică
faptul că putem aplica algoritmul lui Lee cu costuri. Adică, în loc să calculăm
o distanță minimă, calculăm un cost minim, iar pentru a obține de fiecare
dată un cost minim pentru celula curentă, ne vom expanda mai întâi din
zonele sigure, iar abia apoi din cele periculoase.
(1, 1, 0)
(1, 2, 1) (2, 1, 0)
(2, 2, 1) (3, 1, 1) (1, 2, 1)
(1, 3, 2) (2, 2, 1) (3, 1, 1)
(4, 1, 2) (1, 3, 2) (2, 2, 1) (3, 2, 1)
(4, 2, 2) (4, 1, 2) (1, 3, 2) (2, 2, 1) (3, 3, 1)
Soluție: Cerința 2
Pentru a doua cerință putem folosi tehnica căutării binare pe rezultat. La
fiecare pas testăm dacă mijlocul intervalului curent reprezintă o greutate
pentru care se poate ajunge din zona (1,1) în zona (n,n). Pentru verificare
putem folosi atât algoritmul lui Lee, cât și un algoritm de fill. Eu am ales fill
recursiv pentru că e mai scurt și oricum nu avem nevoie de lungimea
drumului minim. Apoi, dacă greutatea este bună, continuăm căutarea binară
în dreapta, în vederea găsirii unei greutăți mai mari. Dacă greutatea nu este
bună, înseamnă că este prea mare, așa că vom căuta în stânga una mai mică.
Sursă C++
Problema Rover
#include <deque>
#include <fstream>
std::ifstream fin("rover.in");
std::ofstream fout("rover.out");
int lin;
int col;
};
// Vectorii de deplasare:
const int dL[] = {-1, 0, 0, 1};
const int dC[] = { 0, -1, 1, 0};
// Datele problemei:
int c, n, g;
int mat[DMAX][DMAX];
// Pentru cerința 1:
int dp[DMAX][DMAX];
std::deque dq;
// Pentru cerința 2:
int sol;
bool aux[DMAX][DMAX];
dp[1][1] = 0;
while (!dq.empty()) {
// Scoatem din deque celula cu costul minim:
cell = dq.front();
dq.pop_front();
// Celula nu e vizitată...
if (dp[nghb.lin][nghb.col] == -1) {
// Dacă celula e sigură, o punem în față:
if (mat[nghb.lin][nghb.col] >= g) {
Lectia nr. 10. – Probleme concurs
dp[nghb.lin][nghb.col] =
dp[cell.lin][cell.col];
dq.push_front(nghb);
}
else { // dacă nu, în spate:
dp[nghb.lin][nghb.col] =
dp[cell.lin][cell.col] + 1;
dq.push_back(nghb);
}
}
}
}
}
int main() {
fin >> c >> n;
if (c == 1)
fin >> g;
if (c == 1) {
leeDeque();
fout << dp[n][n] << '\n';
}
else {
// Căutare binară pe greutatea maximă:
int md, lo = 0, hi = 10001;
while (hi - lo > 1) {
md = (lo + hi) / 2;
if (fillMat(md)) {
lo = md;
sol = md > sol ? md : sol;
}
else
hi = md;
}
fout << sol << '\n';
}
return 0;
}
Anexa
(1,1,1,1),(1,1,2),(1,2,1),(1,3),(2,1,1),(2,2),(3,1),(4)
[1,1,1,1],[1,1,2],[1,3],[2,2],[4]
p(n)= 𝐶𝑛−1
0 1
+𝐶𝑛−1 𝑛−1
+⋯+𝐶𝑛−1 =2n−1
Lectia nr. 10. – Probleme concurs
O serie este un șir infinit între elementele căruia se pune semnul ++.
Implementare
Mai jos aveți o sursa la problema Crescător2 de pe InfoArena, care, după cum
am spus și la început, cere determinarea sumei p(1)+p(2)+⋯+p(n). Mai
întâi am precalculat numerele pentagonale până la n, iar apoi am calculat
dinamica mergând la fiecare pas până la cel mai apropiat număr pentagonal
de i.
Problema Crescător2
#include <bits/stdc++.h>
using namespace std;
ifstream fin("crescator2.in");
ofstream fout("crescator2.out");
int main() {
int n; fin >> n;
vector<int> pent(n);
Lectia nr. 10. – Probleme concurs
Complexitate
Complexitatea soluției este O(n), pentru că numărul pentagonal maxim
până la care se iterează la fiecare pas este aproximativ g(2n/3). Asta se
înmulțește cu 2, deoarece pentru fiecare k luăm în considerare și g(+k) și
g(−k). Așadar, constanta din spatele complexității este de
aproximativ .