Sunteți pe pagina 1din 20

Lectia nr. 3.

– Elemente de combinatorica si jocuri combinatoriale

Elemente de combinatorica si jocuri combinatoriale

Analiza combinatorică este un domeniu al matematicii care


studiază modalitățile de numărare ale elementelor mulțimilor finite,
precum și de alegere și aranjare a lor. Numeroase concepte specifice
acestui domeniu intervin și în probleme de algoritmică.

Produsul cartezian
Fie n∈N și mulțimile A1, A2,…, An. Produsul cartezian A1×A2×…× An
este mulțimea n-uplurilor (x1,x2,…,xn) cu proprietatea că x1∈A1, x2∈A2,
…, xn∈An.
Regula produsului: Dacă mulțimile A1, A2., …, An au respective k1,k2.,
…,kn atunci numărul de elemente al produsului cartezian A1×A2×…
×An este k1⋅k2⋅…⋅kn.

Submulțimile unei mulțimi


Fie A o mulțimi cu n elemente. Atunci ea are 2n submulțimi:
Exemplu: Mulțimea A=1,2,3 are 23=8 submulțimi:

1. ∅
2. 1 ; 2 ; 3
3. 1,2 ;1,3 ; 2,3
4. 1,2,3

Permutări
Se numește permutare o corespondență biunivocă (bijecție) între o
mulțime finită și ea însăși.

Altfel spus, se numește permutare a unei mul țimi finite o aranjare a


elementelor sale într-o anumită ordine.

Exemplu: permutările mulțimii {1,2,3} sunt: (1,2,3), (1,3,2), (2,1,3),


(2,3,1), (3,1,2) și (3,2,1). Într-o altă reprezentare, acestea sunt:

(123123),(123132),(123213),(123231),(123312),(123321)
Dacă σ=(123312), atunci σ(1)=3,σ(2)=1 și σ(3)=2.
Numărul de permutări al unei mulțimi cu n elemente este Pn=n!
=1⋅2⋅⋅…⋅n.
Acest articol conține mai multe despre factorialul unui număr.
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Numărul permutărilor unei mulțimi crește foarte repede. Pentru valori


mici ale lui n, numărul permutărilor depășește domeniul de valori al
datelor întregi, fiind necesară implementarea operațiilor pe numere
mari.

Problema
Fie un număr natural n. Să se afișeze, în ordine lexicografică,
permutările mulțimii {1,2,,⋯,n}{1,2,,⋯,n}.

Exemplu
Pentru n=3, se va afișa:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

Rezolvare
Bineînțeles, vom rezolva problema prin metoda backtracking. Vectorul
soluție x[] va reprezenta o permutare candidat. Să ne gândim care
sunt proprietățile unei permutări, pe care le va respecta și vectorul x[]:

 elementele sunt numere naturale cuprinse între 1 și n;


 elementele nu se repetă;
 vectorul x[] se construiește pas cu pas, element cu element. El
va conține o permutare validă când va conține n elemente,
desigur corecte.

Cele observate mai sus ne permit să precizăm condi țiile specifice


algoritmului backtracking, într-un mod mai formal:

 condiții externe: x[k]∈{1,2,⋯,n};


 condiții interne: x[k]∉{x[1],x[2],⋯,x[k−1]}, pentru k∈{2,3,⋯,n}
 condiții de existență a soluției: k=n

Sursă C++
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Următorul program afișează pe ecran permutările, folosind un algoritm


recursiv:

#include <iostream>
using namespace std;
int x[10] ,n;
void Afis()
{
for( int j=1;j<=n;j++)
cout<<x[j]<<" ";
cout<<endl;
}
bool OK(int k){
for(int i=1;i<k;++i)
if(x[k]==x[i])
return false;
return true;
}
bool Solutie(int k)
{
return k == n;
}
void back(int k){
for(int i=1 ; i<=n ; ++i)
{
x[k]=i;
if( OK(k) )
if(Solutie(k))
Afis();
else
back(k+1);
}
}
int main(){
cin>>n;
back(1);
return 0;
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Varianta iterativă
În general, algoritmii nerecursivi sunt mai buni decât cei recursivi, deși uneori sunt mai dificil
de urmărit. Următorul program iterativ afișează și el permutările pe ecran:

#include <iostream>
using namespace std;
int n , x[100];
void afisare(int k){
for(int i = 1 ; i <= k ; i ++)
cout << x[i] << " ";
cout << endl;
}
bool OK(int k){
for(int i = 1 ; i < k ; i ++)
if(x[i] == x[k])
return 0;
return 1;
}
void back(){
int k = 1;x[1] = 0;
while(k > 0)
{
bool gasit = false;
do{
x[k] ++;
if(x[k] <= n && OK(k))
gasit = true;
}while(! gasit && x[k] <= n);
if(! gasit) k --;
else
if(k < n){
k ++;
x[k] = 0;
}
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

else afisare(k);
}
}
int main(){
cin >> n;
back();
return 0;
}

Probleme pbinfo: 123,124,125,1327,194,202,205,3331

Permutări cu repetiție
Considerăm un set de n obiecte de k.tipuri. Avem n1 obiecte de tipul 1,
n2 obiecte de tipul 2, …, nk obiecte de tipul k și n1+n2+…+nk=n.
Numărul de permutări distincte ale celor n obiecte este:
PR(n)=n! / (n1!⋅n2!⋅…⋅nk!)
Exemplu: Numărul de anagrame distincte ale cuvântului ABABA este:

PR(2+3)=(2+3)! / (2!⋅3!)=1⋅2⋅3⋅4⋅5 / (1⋅2⋅1⋅2⋅3)=10


Acestea sunt: AAABB, AABAB, AABBA, ABAAB, ABABA, ABBAA, BAAAB, BAABA, BABAA,
BBAAA.

Probleme pbinfo: 2205

Aranjamente
Dacă A este o mulțime cu n elemente, submulțimile ordonate cu k
elemente ale lui A, 0 ≤ k ≤ n se numesc aranjamente a n elemente luate
câte k.

Numărul de aranjamente de n luate câte k se notează


k
An =n⋅(n−1)⋅(n−2)⋅…⋅(n−k+1)⋅(n−k)=n! / (n−k)!.
La fel ca în cazul permutărilor, pentru determinarea num ărului de
aranjamente poate fi necesară implementarea operațiilor pe numere
mari.

Probleme pbinfo: 196,3159,1128

Combinări
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Pentru o mulțime dată o combinare reprezintă o modalitate de alegere a


unor elemente, fără a ține cont de ordinea lor. Se numesc combin ări de
k elemente submulțimile cu k elemente ale mulțimii, presupuse cu n
elemente. Numărul de asemenea submulțimi se numește combinări de n
luate câte k și se notează Cnk.
Formule:

 Cnk=Ank / Pk
 Cnk=n⋅(n−1)⋅…⋅(n−k+1). / (1⋅2⋅…⋅k)
 Cnk=n! / (k!⋅(n−k)!)
 Cnk={ 1 dacă k=0,

(n–k+1)/k⋅Cnk altfel.

 Cnk={ 1 dacă n=k sau k=0,

Cn−1k−1+Cn−1k altfel.; această formulă se regăsește


în Triunghiul lui Pascal

Probleme pbinfo: 197,3152,3160,3330

Probleme infoarena

https://www.infoarena.ro/problema/superbec

Triunghiul lui Pascal


este un tablou triunghiular cu numere naturale, în care fiecare element aflat pe laturile
triunghiului are valoarea 1, iar celelalte elemente sunt egale cu suma celor două elemente
vecine, situate pe linia de deasupra.

Proprietăți
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Putem rearanja elementele tabloului astfel:

Considerând liniile și coloanele numerotate ca mai sus atunci:


Ai,j={1 dacă i=j sau j=0,
Ai−1,j−1+Ai−1,j altfel..
De fapt, această relație este cunoscută în calculul combinărilor:
Cnk={1 dacă n=k sau k=0,
k−1 k
C n−1 +C n−1 altfel., unde Cnk înseamnă “combinări den luate câte k”.
De fapt, un element Ai,j al tabloului de mai sus este egal cu Cij.
Elementele de pe linia n sunt coeficienți binomiali ai dezvoltării
(a+b) =Cn ⋅a +Cn ⋅a ⋅b +C ⋅a ⋅b2+⋯+Cn ⋅a ⋅b +⋯+C
n 0 n 1 n−1 1
n
2 n−2 k n−k k
n
n−1
⋅a ⋅b
1
+Cn ⋅bn
n−1 n

binomul lui Newton.
Suma elementelor de pe linia n este egală cu 2n: Cn0+Cn1+Cn2+⋯+Cnk+⋯+Cnn=2n.

Problemă
Se citește un număr natural n. Afișați linia n din triunghiul lui Pascal.
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Soluție cu combinări
O primă soluție constă în calcularea valorii Cnk pentru fiecare k∈{0,1,2,⋯, n}.

#include <iostream>
using namespace std;
int comb(int n , int k)
{
int p = 1;
for(int i = 1 ; i <= k ; i ++)
p = p * (n-i+1) / i;
return p;
}
int main()
{
int n;
cin >> n;
for(int i = 0 ; i <= n ; i ++)
cout << comb(n,i) << " ";
return 0;
}

O îmbunătățire a variantei de mai sus este:


#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int p = 1;
cout << "1 ";
for(int i = 1 ; i <= n ; i ++)
{
p = p * (n-i+1) / i;
cout << p << " ";
}
return 0;
}
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Soluție cu matrice
În soluția următoare aplicăm formula Ai,j=Ai−1,j−1+Ai−1,j, folosind un tablou
unidimensional:
#include <iostream>
using namespace std;
int main()
{
int n, A[31][31];
cin >> n;
for(int i = 0 ; i <= n ; i ++)
{
A[i][0] = A[i][i] = 1;
for(int j = 1 ; j < i ; j ++)
A[i][j] = A[i-1][j-1] + A[i-1][j];
}
for(int j = 0 ; j <= n ; j ++)
cout << A[n][j] << " ";
return 0;
}

Soluție cu doi vectori


Soluția de mai sus poate fi îmbunătățită, observând că pentru a calcula linia curentă folosim
doar linia precedentă. Vom folosi doar doi vectori, unul pentru linia curentă, celălalt pentru
linia precedentă:
#include <iostream>
using namespace std;

int main()
{
int n, v[31],u[31];
cin >> n;
v[0] = 0;
for(int i = 1 ; i <= n ; i ++)
{
u[0] = u[i] = 1;
for(int j = 1 ; j < i ; j ++)
u[j] = v[j-1] + v[j];
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

for(int j = 0 ; j <= i ; j ++)


v[j] = u[j];
}
for(int j = 0 ; j <= n ; j ++)
cout << v[j] << " ";
return 0;
}

Soluție cu doi vectori și doi pointeri


În soluția de mai sus putem evita copierea elementelor din u în v, folosind doi pointeri
suplimentari. Aceștia vor memora adresa de început a celor două tablouri, iar copierea
elementelor este înlocuită de interschimbarea valorilor celor doi pointeri:
#include <iostream>
using namespace std;

int main()
{
int n, A[31], B[31];
int * u = A, * v = B;
cin >> n;
v[0] = 0;
for(int i = 1 ; i <= n ; i ++)
{
u[0] = u[i] = 1;
for(int j = 1 ; j < i ; j ++)
u[j] = v[j-1] + v[j];
swap(u,v);
}
for(int j = 0 ; j <= n ; j ++)
cout << v[j] << " ";
return 0;
}

Soluție cu un singur vector


Putem folosi un singur vector. Acesta conține linia anterioară și se modifică pas cu pas,
pentru a deveni linia curentă a triunghiului:
#include <iostream>
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

using namespace std;


int main()
{
int n, v[31];
cin >> n;
v[0] = 0;
for(int i = 1 ; i <= n ; i ++)
{
v[i] = 1;
for(int j = i - 1 ; j > 0 ; j --)
v[j] = v[j] + v[j-1];
v[0] = 1;
}
for(int j = 0 ; j <= n ; j ++)
cout << v[j] << " ";
return 0;
}

Probleme pbinfo: 702,1718,1110,2449,1297

Probleme infoarena:

https://www.infoarena.ro/problema/magic4

https://www.infoarena.ro/problema/xor3

Numerele lui Catalan


Termenul general al acestui șir este:

n
n+ k
Cn=C –C 2n
n
2n
n+1
=1/(n+1)⋅C =∏ 2n
n
k
,pentru n≥0
k =2

O formulă recursivă:
n
C n+1=∑ C i ∙C n−i
i=0

Pentru n=0,1,2,3,… numere Catalan sunt:


1,1,2,5,14,42,132,429,1430,4862,….
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Există numeroase probleme care au ca rezultat numerele lui Catalan.


Amintim:

 Cn este egal cu numărul de șiruri de n paranteze deschise și n


paranteze închise care se închid corect. Pentru n=3, avem C3=5
variante: ((())), (())(), ()(()), ()()() și (()()).
 numărul de cuvinte Dyck de lungime 2*n: formate din n caractere
X și n caractere Y și în orice prefix umărul de X este mai mare sau
egal cu numărul de Y. Pentru n=3: XXXYYY, XXYYXY, XYXXYY, XYXYXY,
XXYXYY.
 numărul de șiruri formate din n de 1 și n de -1 astfel încât toate
sumele parțiale să fie nenegative.
 Cn reprezintă numărul de modalități de a paranteza o expresie
formată din n+1 operanzi ai unei operatii asociative: ((ab)c)d,
(a(bc))d, (ab)(cd), a((bc)d), a(b(cd)).
 Cn reprezintă numărul de modalităti de a desena “șiruri de munți”
cu n linii crescătoare și n linii descrescătoare. Munții încep și se
termină la același nivel și nu coboară sub acel nivel:

/\
/\ /\ /\/\ / \
/\/\/\ / \/\ /\/ \ / \ / \

 numărul de drumuri laticiale de la punctul (0,0) la (2*n,0) cu pași


din {(1,1),(1,-1) care nu coboară sub axa Ox.
 Cn este numărul de modalități de a triangula un poligon cu n+2
laturi:

 Cn este umărul de drumuri laticiale de la (0,0) la (n,n) cu pași din


{(0,1),(1,0)} care nu depășesc prima diagonală y=x. Următoarea
imagine conține drumurile pentru n=4:
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

 numărul de siruri 1⩽a1⩽a2⩽…⩽an cu proprietatea că ak⩽k este


Cn.
 Cn numărul de modalități de a acoperi o scară cu n trepte folosind
n dreptunghiuri. Pentru n=4:

 pe un cerc sunt 2*n puncte. Numărul posibilități de a desena n


coarde care nu se intersectează este Cn.

Alte probleme care au care rezultat numărul lui Catalan sunt


pe Wikipedia sau în documentul Exercises on Catalan and Related
Numbers, Cambridge University Press, Richard P_ Stanley, June 1998.

Probleme pbinfo: 2917,1257

Probleme inforena :

https://www.infoarena.ro/problema/permutare4

https://www.infoarena.ro/problema/colors

Soluție Catalan cu numere mari

#include<iostream>
#define NMax 1000
using namespace std;
typedef short Huge[NMax+3];
void AtribValue(Huge H,short X) {
H[0] = 0;
while (X) {
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

++H[0];
H[H[0]] = X % 10;
X /= 10;
}
}
void Mult(Huge H,short X)
/* H <- H*X */
{short i,T=0;
for (i=1;i<=H[0];i++)
{ H[i]=H[i]*X+T;
T=H[i]/10;
H[i]=H[i]%10;
}
while (T) /* Cat timp exista transport */
{ H[++H[0]]=T%10;
T/=10;
}
}

void Divide(Huge A,short X)


/* A <- A/X si intoarce A%X */
{ short i,R=0;
for (i=A[0];i;i--)
{ A[i]=(R=10*R+A[i])/X;
R%=X;
}
while (!A[A[0]] && A[0]>1) A[0]--;
}

void Afisez(Huge H){


for(short i=H[0];i>0;--i) cout<<H[i];
cout<<'\n';
}

void catalan(unsigned int n,unsigned int k)


/// Folosesc coeficientii binomiali
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

{
Huge res;
AtribValue(res,1);
if(k>n-k) /// C(n, k) = C(n, n-k)
k=n-k;
for(unsigned int i=0;i<k;++i)
/// Calculez valoarea [n*(n-1)*---*(n-k+1)]/[k*(k-1)*---*1]
{
Mult(res,n-i); /// res*=n-i
Divide(res,i+1); /// res/=(i+1)
}
Divide(res,n/2+1); /// res/(n/2+1)
Afisez(res);
}
int main(){
int n;
cin>>n;
catalan(2*n,n);
return 0;
}

Soluție PD pentru Catalan :

#include <iostream>
using namespace std;
unsigned long int catalanDP(unsigned int n)
{
unsigned long int catalan[n + 1];
catalan[0] = catalan[1] = 1;
for (int i = 2; i <= n; i++) {
catalan[i] = 0;
for (int j = 0; j < i; j++)
catalan[i] += catalan[j] * catalan[i - j - 1];
}
return catalan[n];
}
int main() {
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

for (int i = 0; i < 10; i++)


cout << catalanDP(i) << " ";
return 0;
}

Partițiile unei mulțimi


Fie A=a1,a2,…,an o mulțime (finită). Se numește partiție a mulțimii A o
familie de submulțimi A1,A2,…,Ak ale lui A cu proprietățile:

1. Ai⊆A,∀i=1,k
2. A1∪A2∪…∪Ak=A
3. Ai∩Aj=∅, ∀i≠j

De exemplu, A=1,2,3 are următoarele partiții:

 1,2,3
 1,2,3, 1,3,2, 1,2,3
 1,2,3

 Numărul de partiții cu k submulțimi ale unei mulțimi cu n elemente


se numește numărul lui Stirling de speța a doua , notat S(n,k). El
respectă următoarea relație de recurență:
S(0,0)=1
S(0,n)=S(n,0)=0
S(n+1,k)=k⋅S(n,k)+S(n,k−1)pentru k>0

Sursă C++ pentru calcularea S(n,k)


Putem folosi metoda programării dinamice pentru calcularea S(n,k): Vom
reține într-o matrice rezultatele subproblemelor, mai exact stir[i][j]=S(i,j).

#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k; cin >> n >> k;
vector<vector<int>> stir(n + 1, vector<int>(k + 1));
stir[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= min(i, k); j++)
stir[i][j]=stir[i-1][j-1]+j*stir[i-1][j];
cout << stir[n][k] << '\n';
return 0;
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

}
 Numerele lui Stirling de speța I numără câte permutări de ordin n
cu k cicluri există, și se notează cu s(n,k).

Cazurile particulare sunt: s(0,0)=1, s(n,0)=0 (n>0) și s(0,k)=0 (k>0).


Motivele sunt similare cu cele de la numerele de speța a II-a.
Recurența este: s(n,k)=s(n−1,k−1)+(n−1)⋅s(n−1,k)

Sursă C++ pentru calcularea s(n,k)


Iată mai jos o sursă care calculează s(n,k). Este aproape identică cu
cea pentru numere lui Stirling de speța a II-a, singura diferen ță fiind
relația de recurență (linia 10).
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k; cin >> n >> k;
vector<vector<int>> stir(n + 1, vector<int>(k + 1));
stir[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= min(i, k); j++)
stir[i][j]=stir[i-1][j-1]+(i-1)*stir[i-1][j];
cout << stir[n][k] << '\n';
return 0;
}

Pentru mai multe informații accesați https://infogenius.ro/numerele-stirling-bell/

Soluție pentru problema de pe pbinfo: Numerele lui Stirling

#include<fstream>
#define MOD 98999
using namespace std;
ifstream fin("stirling.in");
ofstream fout("stirling.out");
int s[202][202],S[202][202];
int main(){
int t,p,n,m,i,j;
s[0][0]=1; S[0][0]=1;
for(i=1;i<=200;i++)
for(j=1;j<=i;j++){
s[i][j]=s[i-1][j]*(1-i)+s[i-1][j-1];
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

s[i][j]%=MOD;
S[i][j]=S[i-1][j]*j+S[i-1][j-1];
S[i][j]%=MOD;
}
fin>>t;
for(i=1;i<=t;i++) {
fin>>p>>n>>m;
if(p==1) fout<<s[n][m];
else fout<<S[n][m];
fout<<'\n';
}
return 0;
}

 Probleme infoarena:, ex, xnumere, trineq, 100m, Permutări, Sistem,


Permutări4

Numărul total de partiții ale unei mulțimi cu n elemente este numărul


lui Bell:
n
Bn=∑ S (n , k )
k=0

Începând cu B0=B1=1, primele numere Bell sunt:


1,1,2,5,15,52,203,877,4140,….
Numerele Bell pot fi construite cu ajutorul așa-numitului triunghi Bell.
Vom construi (parțial) un tablou A[][]:

 A[0][1] = 1
 primul element de pe liniile următoare este egal cu ultimul de pe
linia precedentă: A[i][1] = A[i-1][i]
 celelalte elemente ale fiecărei linii sunt egale cu suma celor după
elemente din stânga (linia curentă și linia precedentă): A[i][j] =
A[i][j-1] + A[i-1][j-1], pentru j=2,..., i+1
 primul element de pe fiecare linie este numărul Bell
corespunzător: B =A[n][1].
n
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

Soluție:

#include<iostream>
using namespace std;
int bellNumber(int n) {
int bell[n+1][n+1];
bell[0][0] = 1;
for (int i=1; i<=n; i++) {
bell[i][0] = bell[i-1][i-1];
for (int j=1; j<=i; j++)
bell[i][j] = bell[i-1][j-1] + bell[i][j-1];
}
return bell[n][0];
}
int main() {
for (int n=0; n<=5; n++)
cout<<"Bell Number "<<n<<" este "<< bellNumber(n)<<endl;
return 0;
}

Numerele Narayana

Vezi paper20.pdf

Soluție

#include<bits/stdc++.h>
using namespace std;
int Nara(int n, int k)
{
Lectia nr. 3. – Elemente de combinatorica si jocuri combinatoriale

int C[n + 1][k + 1];


for (int i = 0; i <= n; i++)
for (int j = 0; j <= min(i, k); j++)
if (j == 0 || j == i) C[i][j] = 1;
else C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
return C[n][k] * C[n][k - 1];
}
int Solve(int n, int k){return (Nara(n, k)) / n;}
int main(){
int n = 8, k = 5;
cout << Solve(n, k) << endl;
return 0;
}

S-ar putea să vă placă și