Sunday, February 18, 2018

কম্বিনেশন ( Combination )

কোন বড় গ্রুপ থেকে কিছু সংখ্যক জিনিস নিয়ে ছোট ছোট গ্রুপে ভাগ করাই হল কম্বিনেশন। এখানে পারমুটেশন এর মত অর্ডার এর কোন প্রভাব থাকেনা। যেমনঃ ৩ টি জিনিস থেকে আমরা ২ টি জিনিস নিতে পারি এভাবেঃ
(১, ২), (১,৩), (২,৩) = মোট ৩ ভাবে, এটিই কম্বিনেশন।

অর্থাৎ, n টি জিনিস থেকে r টি জিনিস কতভাবে নিতে পারি সেটিই হল n,k এর কম্বিনেশন। (এখানে k<=n)


বাইনোমিয়াল কো-এফিশিয়েন্ট ( Binomial Coefficient )

বাইনোমিয়াল কোএফিশিয়েন্ট হল অর্ডার বিবেচনা না করে ভিন্ন ভিন্ন সম্ভাবনাকে নেয়া। শুনতে কঠিন লাগলেও ব্যাপারটি আসলে কিছুইনা। আমরা n জিনিস থেকে k জিনিস নিবো, অর্ডার বিবেচনা না করে। এটিকে কতভাবে নেয়া যাবে, এটিই বাইনোমিয়াল কোএফিশিয়েন্ট, অর্থাৎ কম্বিনেশন। 
আমরা কিছু সিরিজ এর এক্সপানশন দেখিঃ

(x + y)0 = 1
(x + y)1 = x + y
(x + y)2 = x2 + 2xy + y2
(x + y)3 = x3 + 3x2y + 3xy2 + y3
(x + y)4 = x4 + 4x3y + 6x2y2 + 4xy3 + y4

এসব সিরিজ কে আমরা এভাবে লিখতে পারি কমন ফর্মেঃ 


আমরা কম্বিনেশন এর জন্য বাইনোমিয়াল কোএফিশিয়েন্ট ব্যবহার করবো, তবে তাকে মনোমিয়াল ফর্মে নিয়ে।
( মনোমিয়াল or monomial হল এমন পলিনোমিয়াল যার শুধুমাত্র একটি টার্ম আছে )। কাজেই আমাদের বাইনোমিয়াল এর রূপ হবে এরকমঃ (1 + x) n
আমরা এই ফর্মকে এক্সপানশন করলে প্রতি পাওয়ার অনুসারে প্রতি টার্ম এর একটি করে কো-এফিশিয়েন্ট পাবো। আমাদের কাজ হবে ঐ কো-এফিশিয়েন্ট নিয়ে কাজ করা। যেমন আমরা কিছু কো-এফিশিয়েন্ট দেখিঃ


আমরা n = 4 নিয়ে দেখিঃ
(1 + x)4 = x4 + 4x3 + 6x2 + 4x + 1
এখন কো-এফিশিয়েন্টগুলি হবেঃ 1  4  6  4  1
এভাবে পাওয়ার অনুযায়ী এক্সপানশন করতে থাকলে আমরা একটি সুন্দর ত্রিভুজ এর মত ডিজাইন পাই, এটি সবার প্রথম খেয়াল করেছেন প্যাসকেল (Pascal). এজন্য একে প্যাসকেলের ত্রিভুজ বলা হয়। প্যাসকেল এর শর্তানুযায়ী আমরা কম্বিনেশন এভাবে দেখতে পারিঃ

(x + y)n = (nC0)(xny0) + (nC1)(xn-11y1) + (nC2)(xn-22y2) + ... + (nCn)(x0yn);  [nCk = combnation of k element from n or n choose k]

আমরা যেহেতু বুঝতে পারলাম বাইনোমিয়াল কো-এফিশিয়েন্ট আসলে কি জিনিস, এখন আমরা কিছু অ্যালগরিদম দেখবো কিভাবে এই বাইনোমিয়াল বের করা যায়ঃ


১। ফ্যাক্টরিয়াল ( Factorial )

আমরা সরাসরি nCk ফর্মুলা ব্যবহার করে nCk বের করতে পারি।
ফর্মুলাঃ nCk  =  [ n! ] / [  k! * (n-k)! ]

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
ll nCr(ll n, ll r)
{
ll f[n + 1];
f[0] = 1;
for (ll i = 1; i <= n; i++)
f[i] = i * f[i-1];
return f[n] / f[r] / f[n-r];
}
int main()
{
ll n,r,m;
while (cin >> n >> r)
{
cout << nCr(n, Min(r,n-r)) << endl;
}
return 0;
}

কিন্তু এভাবে আমরা সর্বোচ্চ ২১-২২ ফ্যাক্টরিয়াল পর্যন্ত হিসাবে করতে পারবো। এর চেয়ে বেশি আমরা সি++ এ করতে গেলে আমাদের বিগ-ইন্টিজার লাইব্রেরি ব্যবহার করতে হবে। তবে পাইথন এ আমরা হিসাব করতে পারবো এবং এই হিসাবের কমপ্লেক্সিটি হল O(n).
আমরা বড় বড় কম্বিনেশন এর জন্য, nCk mod m এরকম কিছু একটা করে রেজাল্ট একটি নির্দিষ্ট লিমিটের মাঝে রাখতে পারি।


২। রিকার্সন ( Recursion )

nCk বের করার রিকার্সন ফর্মুলা হলঃ nCk (i,k) = nCk (i-1,k-1) + nCk (i-1,k);
আমাদের টাইম এবং স্পেস কমপ্লেক্সিটি হবেঃ O(n*k) for nCk

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
ll nCr(ll n, ll r, ll MOD)
{
vector< vector<ll> > nCr(n+1,vector<ll> (r+1,0));
for(ll i = 0; i <= n; i++)
{
for(ll k = 0; k <= r && k <= i; k++)
if(k == 0 || k == i)
nCr[i][k] = 1;
else
nCr[i][k] = (nCr[i-1][k-1] + nCr[i-1][k])%MOD;
}
return nCr[n][r];
}
int main()
{
ll n,r,m;
while (cin >> n >> r >> m)
{
cout << nCr(n, r, m) << endl;
}
return 0;
}
আমরা একে একটু মডিফাই করে স্পেস কমপ্লেক্সিটি কমিয়ে আনতে পারি। আমরা আগের সারি চেক করবো, কারন আমাদের ঐ সারিগুলো আসলে দরকার হবেনা পরে আর।

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
ll nCr(ll n, ll r, ll MOD)
{
vector< vector<ll> > nCr(2,vector<ll> (r+1,0));
for(ll i = 0; i <= n; i++)
{
for(ll k = 0; k <= r && k <= i; k++)
if(k == 0 || k == i)
nCr[i&1][k] = 1;
else
nCr[i&1][k] = (nCr[(i-1)&1][k-1] + nCr[(i-1)&1][k])%MOD;
}
return nCr[n&1][r];
}
int main()
{
ll n,r,m;
while (cin >> n >> r >> m)
{
cout << nCr(n, r, m) << endl;
}
return 0;
}

৩। কম্বিনেশন এর বিস্তার ( Expansion of Combination )

আমরা জানি, nCk  =  [ n! ] / [  k! * (n-k)! ], অর্থাৎ

               [ n(n-1)(n-2) ... (n-k+1) ] [ (n-k) ... (1) ]
-------------------------------------------------------------------------
                  [ (n-k) ... (1) ] [ k(k-1)(k-2) .... (1) ]

আমরা লব এবং হর থেকে [ (n-k) ... (1) ] টার্ম কেটে ফেলতে পারি, তাহলে আমাদের ফাইনাল টার্ম হবেঃ

 n       (n-1)      (n-2)      (n-k+1)
---  *  ------  *  ------  *  ----------
 k       (k-1)      (k-2)          (1)

আমরা একে এভাবে লিখতে পারিঃ

nCk (n,k) = 1                                             ; if k == 0
                     else  (n/k) * nCk (n-1, k-1)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
ll nCr(ll n, ll r)
{
if (r==0) return 1;
else return nCr(n-1,r-1) * n / r;
}
int main()
{
ll n,r,m;
while (cin >> n >> r)
{
cout << nCr(n, Min(r, n-r)) << endl;
}
return 0;
}

৪। প্রাইম এর পাওয়ার ( Power of Prime )

আমরা ফ্যাক্টরিয়াল এর কোন প্রাইম এর পাওয়ার বের করতে পারি এভাবেঃ

Pprime = floor[ n/prime ] + floor[ n/prime2 ] + .....

এখানে n = factorial, prime = the prime whose power in n-factorial we are calculating
আমরা nCk এর মাঝে p প্রাইম এর পাওয়ার বের করবো এভাবেঃ
power = fact(n, p) - fact(r, p) - fact(n-r, p)
এরপরে আমরা ppower করবো সকল প্রাইম, p এর জন্য যারা n থেকে ছোট।

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
/* This function calculates power of p in n! */
ll fact(ll n, ll p)
{
ll k = 0;
while (n > 0)
{
k += n/p;
n /= p;
}
return k;
}
/* This function calculates (a^b)%MOD */
ll modPow(ll a, ll b, ll MOD)
{
ll x = 1, y = a;
while(b > 0)
{
if(b%2 == 1)
{
x = (x*y);
if(x > MOD) x %= MOD;
}
y = (y * y);
if(y > MOD) y %= MOD;
b /= 2;
}
return x;
}
ll nCr(ll n, ll r, ll MOD)
{
ll res = 1;
vector<bool> isPrime(n+1,1);
for(ll i = 2; i <= n; i++)
if(isPrime[i])
{
for(ll j = 2*i; j <= n; j += i)
isPrime[j]=0;
ll power = fact(n,i) - fact(r,i) - fact(n-r,i);
res = (res * modPow(i, power, MOD)) % MOD;
}
return res;
}
int main()
{
ll n,r,m;
while (cin >> n >> r >> m)
{
cout << nCr(n, r, m) << endl;
}
return 0;
}

৫। চাইনিজ রিমেইন্ডার থিওরেম  ( Chinese Remainder Theorem )

যদি আমাদের এরকম বের করতে বলা হয়ঃ nCk mod m where m is not prime , সেক্ষেত্রে আমরা m কে প্রাইম এ ফ্যাক্টরাইজ করার পরে CRT(Chinese Remainder Theorem) ব্যবহার করে মান বের করবো।

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Min(a,b) (a<=b)? a : b
/* This function calculates (a^b)%MOD */
ll modPow(ll a, ll b, ll MOD)
{
ll x = 1, y = a;
while(b > 0)
{
if(b%2 == 1)
{
x = (x*y);
if(x > MOD) x %= MOD;
}
y = (y * y);
if(y > MOD) y %= MOD;
b /= 2;
}
return x;
}
/* Modular Multiplicative Inverse
Using Euler's Theorem
a^(phi(m)) = 1 (mod m)
a^(-1) = a^(m-2) (mod m) */
ll invMod(ll n, ll MOD)
{
return modPow(n,MOD-2,MOD);
}
ll nCr(ll n, ll r, ll MOD)
{
vector<ll> f(n + 1,1);
for(ll i = 2; i <= n; i++)
f[i] = (f[i-1] * i) % MOD;
return (f[n]*((invMod(f[r], MOD) * invMod(f[n-r], MOD)) % MOD)) % MOD;
}
int main()
{
ll n,r,m;
while (cin >> n >> r >> m)
{
cout << nCr(n, r, m) << endl;
}
return 0;
}
view raw nCk_CRT.cpp hosted with ❤ by GitHub


No comments:

Post a Comment