Simulări
O simulare e un program care imită pas cu pas un proces din lumea reală sau dintr-un scenariu abstract. În loc să găsim o formulă directă sau un algoritm complicat, urmăm instrucțiunile problemei literal — poziție cu poziție, tact cu tact.
Multe probleme de olimpiadă (mai ales la clasele mici) sunt exact asta: nu cer vreo idee genială, ci doar să transpui corect în cod ce zice enunțul.
Când folosești simulare?
- Enunțul descrie un proces cu pași repetitivi: „la fiecare secundă…“, „pentru fiecare rundă…”, „un robot se mișcă…”
- Limitele sunt mici
(
N, T ≤ 10⁴ – 10⁵), deci îți permiți să execuți toți pașii. - Nu vezi o formulă simplă sau un pattern matematic.
- Regulile sunt clare dar complexe — un algoritm „deștept” ar fi greu de conceput, dar respectarea regulilor literal e directă.
Ce fac eu?
Înainte să încep o rezolvare, încerc să descopăr la ce s-a gândit autorul…
Componentele unei simulări
Orice simulare are trei piese:
1. Reprezentarea sistemului
Cum stocăm informația despre „lumea” pe care o simulăm.
- Variabile simple (poziții
x, y, vitezevx, vy, un contor). - Vectori / matrici (o hartă, o listă de obiecte).
- Struct-uri (când fiecare obiect are mai multe atribute).
2. Starea sistemului
O „fotografie” a tot ce e necesar la un moment dat. Trebuie să alegi ce stochezi:
// Exemplu: minge pe grilă
int x, y; // poziția
int dx, dy; // direcția
int pas; // al câtelea pasRegulă: starea trebuie să conțină tot ce ai nevoie pentru a calcula următorul pas. Dacă uiți un detaliu, simularea iese greșită.
3. Bucla de evenimente (time loop)
O buclă care avansează starea un pas o dată:
cât timp nu s-a terminat simularea:
1. privești starea curentă
2. aplici regulile → starea nouă
3. (opțional) notezi/afișezi ceva
Șablon general
// 1. inițializează starea
citește datele inițiale;
// 2. simulează T pași
for (int t = 1; t <= T; t++) {
// aplică regulile de tranziție
calculează noua stare pe baza celei vechi;
actualizează variabilele;
}
// 3. afișează rezultatul final
fout << stareaFinala;Exemplu 1: minge care sare într-un dreptunghi
O minge pornește din
(x, y)și se mișcă cu direcția(dx, dy)într-un dreptunghiN × M. La fiecare pas își schimbă poziția. Când lovește un perete, direcția se inversează pe axa respectivă. Unde se află dupăTpași?
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int main() {
int N, M, x, y, dx, dy, T;
fin >> N >> M >> x >> y >> dx >> dy >> T;
for (int t = 1; t <= T; t++) {
x += dx;
y += dy;
// reflexie pe pereți
if (x < 1) { x = 2 - x; dx = -dx; }
if (x > N) { x = 2 * N - x; dx = -dx; }
if (y < 1) { y = 2 - y; dy = -dy; }
if (y > M) { y = 2 * M - y; dy = -dy; }
}
fout << x << " " << y;
return 0;
}Starea: (x, y, dx, dy).
Tranziția: poziție += direcție, apoi reflexie
la perete.
Complexitate: O(T).
Exemplu 2: turist pe hartă cu direcții
Un turist primește o secvență de comenzi:
N(nord),S(sud),E(est),V(vest). Pornește din(0, 0). Care e cea mai lungă distanță de origine pe care o atinge?
#include <fstream>
#include <string>
#include <cmath>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int main() {
string cmd;
fin >> cmd;
int x = 0, y = 0;
int maxDist = 0;
for (char c : cmd) {
if (c == 'N') y++;
else if (c == 'S') y--;
else if (c == 'E') x++;
else if (c == 'V') x--;
int dist = x * x + y * y;
if (dist > maxDist) maxDist = dist;
}
fout << maxDist; // pătratul distanței (să evităm sqrt)
return 0;
}Truc: comparăm pătratele
distanțelor — evităm sqrt (mai lent și
impreciz).
Exemplu 3: coadă la bancă (event-driven)
Nclienți vin la bancă. Clientulisosește la momentult[i]și are nevoie ded[i]minute. Există un singur casier. Calculează timpul total de așteptare.
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int main() {
int N;
fin >> N;
int timpCurent = 0;
long long asteptareTotala = 0;
for (int i = 0; i < N; i++) {
int t, d;
fin >> t >> d;
if (timpCurent < t) timpCurent = t; // casierul așteaptă clientul
else asteptareTotala += timpCurent - t; // clientul așteaptă casierul
timpCurent += d; // clientul e servit
}
fout << asteptareTotala;
return 0;
}Starea: timpCurent (când se
eliberează casierul).
Eveniment: sosirea unui client.
Regulă: max(timpCurent, t) +
d.
Exemplu 4: evaporarea unei matrici
Avem o matrice
N × Mcu valori. La fiecare secundă, fiecare celulă își pierde 1 unitate (dar nu coboară sub 0). Câte secunde până toată matricea devine 0?
int maxim = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j] > maxim) maxim = a[i][j];
fout << maxim; // numărul de secunde = cea mai mare valoareOptimizare: aici simularea pas cu pas ar fi O(N·M·T). Dar observăm că răspunsul e chiar maximul din matrice → O(N·M). Când poți, înlocuiește simularea cu raționament matematic.
Exemplu 5: Jocul Vieții (Game of Life simplu)
Matrice de
N × Ncelule. La fiecare tact:
- O celulă moartă cu exact 3 vecini vii devine vie.
- O celulă vie cu 2 sau 3 vecini vii rămâne vie. Altfel, moare.
Câte celule vii sunt după
Ttacturi?
int a[50][50], b[50][50];
int N, T;
int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
void pas() {
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++) {
int vecini = 0;
for (int d = 0; d < 8; d++) {
int ni = i + dx[d], nj = j + dy[d];
if (ni >= 0 && ni < N && nj >= 0 && nj < N && a[ni][nj] == 1)
vecini++;
}
if (a[i][j] == 1) b[i][j] = (vecini == 2 || vecini == 3) ? 1 : 0;
else b[i][j] = (vecini == 3) ? 1 : 0;
}
// copiez b în a
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++) a[i][j] = b[i][j];
}Regulă de aur pentru simulări pe matrice cu
vecini: folosește două matrici — una veche, una
nouă. Altfel, modificările făcute la (i, j)
afectează calculul pentru (i+1, j) și strici
totul.
Șablon: simulare cu două stări (copie ↔︎ original)
Pentru procese simultane (toate celulele se actualizează în același tact):
int a[NMAX][NMAX]; // starea curentă
int b[NMAX][NMAX]; // starea următoare
for (int t = 1; t <= T; t++) {
// calculează b din a
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
b[i][j] = regulaTranzitie(a, i, j);
// copiază b în a
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
a[i][j] = b[i][j];
}Sau — truc memorie — folosește un singur
tablou 3D a[2][N][M] și alternează indicele
t % 2:
int a[2][NMAX][NMAX];
int cur = 0;
for (int t = 1; t <= T; t++) {
int next = 1 - cur;
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
a[next][i][j] = regulaTranzitie(a[cur], i, j);
cur = next;
}
// răspunsul e în a[cur]Evită copierea explicită.
Când simularea devine prea lentă
Dacă T = 10⁹, simularea directă O(T) nu merge.
Soluții:
1. Găsește un ciclu
Multe sisteme intră într-o stare care se repetă după un număr finit de pași. Detectezi ciclul și răspunzi direct.
mapare: stare → primul tact la care a apărut
la fiecare pas:
dacă stare nouă e deja în mapare:
ciclu găsit, calculezi direct t % ciclu
altfel:
adaugi în mapare
2. Observă invarianți
Dacă suma/maximul/alt ceva nu se schimbă, poți răspunde fără simulare.
3. Ridicarea la putere a matricilor
Pentru tranziții liniare (Fibonacci, grafuri), se folosește exponențiere rapidă pe matrice → O(log T).
4. Simulare leneșă
Nu calculezi ce nu ți se cere. Procesează doar evenimente cheie, sari peste „timpul mort”.
Greșeli frecvente
1. Modificarea stării curente în timpul actualizării
// GREȘIT la matrice cu vecini
for (i, j)
a[i][j] = regula(a); // următoarea celulă vede valoarea modificată!Folosește două matrici (sau o matrice 3D cu toggle).
2. Bucle infinite prin uitarea condiției de oprire
while (!gata) {
// ...
if (conditie) gata = true; // uitat în niște ramuri
}Mereu pune un maxim de pași ca siguranță:
for (int t = 0; t < MAX_PAȘI && !gata; t++).
3. Off-by-one la indici
Dacă matricea e 1..N × 1..M, vecinii
(0, j) sau (N+1, j) sunt în
afară. Verifică întotdeauna:
if (ni >= 1 && ni <= N && nj >= 1 && nj <= M) { ... }4. Starea incompletă
Uiți să stochezi ceva ce afectează tranziția (ex. direcția mingii). Rezultat: simulare greșită, „sare” aleator.
5. Efecte de ordine
La simulări „secvențiale” (fiecare celulă se actualizează pe rând), ordinea de iterare poate schimba rezultatul. Atenție la enunț: „simultan” vs „unul câte unul”.
6. T mare cu
simulare O(T)
Verifică complexitatea înainte. Dacă T ≥ 10⁹,
caută ciclu sau formulă — nu merge O(T).
Checklist pentru probleme de simulare
Înainte să scrii cod:
Ce să reții
- Simularea = urmezi regulile problemei pas cu pas, fără șmecherii.
- Trei componente: reprezentare (variabile) + stare (variabilele la momentul curent) + buclă de evenimente (regulile de tranziție).
- Șablon: inițializare →
for t = 1..T: aplică tranziția→ afișează. - Pentru procese simultane pe matrice: folosește două tablouri (vechi → nou).
- Pentru vecini: vectori de direcție
dx[], dy[]. - Evită
sqrtcând poți compara cu pătratele distanțelor. - Dacă
Te enorm: caută cicluri, invarianți sau formulă. - Simularea e directă — cea mai obișnuită eroare e modificarea stării curente în timp ce o folosești ca input.