এই পর্বে যা শিখবেন
- অচেনা ধাঁধায় ছোট ক্ষেত্র, প্যাটার্ন, প্রমাণ আর যাচাইয়ের ক্রমে জোরে ভাবা
- Pigeonhole, decision tree, backward induction আর invariant দিয়ে ধাঁধার উত্তর প্রমাণ করা
- Linearity of expectation আর অবস্থার সমীকরণে প্রত্যাশিত মান হাতে বের করা
- Bit trick, modular inverse আর graph-এর মৌলিক উপপাদ্য কোডিং ইন্টারভিউর ভাষায় বলা
- Recurrence আর DP দিয়ে গণনা, আর Nim-এর XOR কৌশলের পূর্ণ প্রমাণ দেওয়া
১ · ইন্টারভিউতে গণিতের প্রশ্ন — জোরে ভাবার পাঁচ ধাপ
রিভিশন: পর্ব ০৮ প্রমাণের পদ্ধতি, পর্ব ০৯ প্রমাণের কৌশল, পর্ব ১০ Mathematical induction
ক্রিকেটের ধারাভাষ্যকার শুধু রান বলেন না, বলেন কেন বোলার এই লাইনে বল করলেন। গণিতের ইন্টারভিউও তেমন: শেষ সংখ্যাটির চেয়ে বেশি দেখা হয় আপনি কীভাবে সেখানে পৌঁছালেন। চুপচাপ পাঁচ মিনিট ভেবে “৯৮” বলার চেয়ে জোরে ভাবা ভালো: “আগে দুজন জলদস্যু নিয়ে দেখি, তারপর তিনজন…”। ইন্টারভিউয়ার তখন আপনার পথ দেখেন, আর ভুল দিকে গেলে একটি সংকেতও দিতে পারেন।
ছবি ৮৩.১ — অচেনা প্রশ্নে পাঁচ ধাপের চক্র
যাচাই না মিললে ফিরে যান ছোট ক্ষেত্রে; পুরো সময় জোরে বলুন
- “আগে নিশ্চিত হই: সবাই কি সমান সম্ভাবনায় আসে? সংখ্যাগুলো কি ধনাত্মক?” — শর্ত জিজ্ঞেস করা দুর্বলতা নয়।
- “n = 1, 2, 3 হাতে করি।” — ছোট ক্ষেত্র প্রায় সব প্যাটার্নের দরজা।
- “অনুমান করছি উত্তর পূর্ণবর্গ; এখন কারণ খুঁজি।” — অনুমান আর প্রমাণ আলাদা করে বলুন।
- “সীমায় মেলাই: n = 0 হলে কী হয়?” — উত্তর বলার আগে একটি সহজ মানে পরীক্ষা।
| ধাপ | |
| যা জোরে বলছেন | |
| উদাহরণ |
- বোঝা। “k-তম মানুষ b টেপেন” মানে k ∣ b. তাই বাল্ব b কতবার টেপা হলো, তা b-এর ভাজকের সংখ্যা। প্রশ্নটি সুইচের নয়, ভাজকের।
- ছোট ক্ষেত্র। ১০টি বাল্ব আর ১০ জন মানুষে হাতে চালান: শেষে জ্বলে থাকে 1, 4, 9. ১২ নম্বর ছয়বার টেপা হয়, তাই নিভে যায়।
- প্যাটার্ন। 1, 4, 9 দেখে অনুমান: পূর্ণবর্গ। জোরে বলুন “এটি এখনো অনুমান”, তারপর কারণ খুঁজুন।
- কারণ। শুরুতে সব নিভানো, প্রতিটি টেপায় অবস্থা উল্টায়। তাই শেষে জ্বলে থাকবে ঠিক সেই বাল্ব, যার ভাজক বিজোড় সংখ্যক।
- প্রমাণ। d ∣ b হলে b/d-ও ভাজক, তাই ভাজকেরা জোড়ায় আসে। জোড়া একা পড়ে কেবল d = b/d, অর্থাৎ d² = b হলে। তাই ভাজক বিজোড় ⇔ b পূর্ণবর্গ। ∎
- যাচাই। ১ থেকে ১০০-এর মধ্যে পূর্ণবর্গ ⌊√100⌋ = ১০টি। অংশ ২-এর শেষ ব্লক সব বাল্ব সত্যিই টিপে একই তালিকা দেয়।
প্র ১ইন্টারভিউতে এমন একটি ধাঁধা পেলেন, যার উত্তর জানেন না, আর দুই মিনিট কিছুই মাথায় আসছে না। কী করবেন? (সহজ)
দুর্বল উত্তর: চুপ করে বসে থাকা, অথবা একটি সংখ্যা আন্দাজে বলে দেওয়া। কেন দুর্বল: ইন্টারভিউয়ার আপনার চিন্তা দেখতে পান না, আর আন্দাজ ভুল হলে ফেরার পথ থাকে না।
শক্ত উত্তর: প্রশ্নটি নিজের ভাষায় আবার বলুন আর শর্ত জিজ্ঞেস করুন। তারপর সবচেয়ে ছোট ক্ষেত্রটি জোরে সমাধান করুন (১টি মুদ্রা, ২ জন জলদস্যু, n = 1). একটি সাধারণ কৌশলের নাম বলে চেষ্টা করুন: “উল্টো দিক থেকে ভাবি”, “কোনো জিনিস কি বদলায় না?”, “খোপ আর কবুতর কী?”। সংকেত পেলে ধন্যবাদ দিয়ে সেটি ব্যবহার করুন। উত্তর না পেলেও যা প্রমাণ করেছেন আর যা অনুমান, তা আলাদা করে বলুন; সৎ আংশিক উত্তর আত্মবিশ্বাসী ভুলের চেয়ে ভালো। রিভিশন: পর্ব ০৯ প্রমাণের কৌশল.
এই পাতা সম্পর্কে: এখানকার কোনো প্রশ্ন কোনো নির্দিষ্ট প্রতিষ্ঠান, পরীক্ষা বা বিশ্ববিদ্যালয় থেকে নেওয়া নয়; এ ধরনের প্রশ্ন software ইন্টারভিউ, aptitude পরীক্ষা আর programming contest-এ প্রায়ই আসে। জলদস্যু বা দাঁড়িপাল্লার মতো কয়েকটি বহুল প্রচলিত পুরোনো ধাঁধা, যাদের উৎস নিশ্চিত নয়; নিয়মগুলো এখানে স্পষ্ট করে ধরে নেওয়া হয়েছে। বারোটি ব্লক ব্রাউজারে Python (Pyodide) চালায়; প্রথমবার নামতে কয়েক সেকেন্ড লাগে। এই মডিউলের পাশের পাতা: পর্ব ৭৮ “ক্যাপস্টোন — বাজারঘরের সমস্যায় discrete math”, পর্ব ৭৯ “Numerical সমস্যার অনুশীলন”, পর্ব ৮০ “MCQ প্রশ্নব্যাংক”, পর্ব ৮১ “লিখিত ও মৌখিক (ভাইভা) প্রশ্ন” আর পর্ব ৮২ “প্রমাণ লেখার অনুশীলন”, যেখানে পূর্ণ প্রমাণ নম্বর-ভাগসহ লেখা আছে।
২ · যুক্তির ধাঁধা — দাঁড়িপাল্লা, হাত মেলানো, দ্বীপবাসী আর জলদস্যু
রিভিশন: পর্ব ০২ Propositional logic, পর্ব ১১ Strong induction, পর্ব ৩২ Pigeonhole principle, পর্ব ৫৭ গ্রাফ — degree আর handshake, পর্ব ৬৫ Tree
প্র ২দেখতে একই রকম ৯টি মুদ্রার একটি একটু ভারী। দুই পাল্লার দাঁড়িপাল্লায় (ওজনের বাটখারা নেই) অন্তত কয়বার মাপলে ভারীটি নিশ্চিতভাবে পাবেন? (সহজ)
উত্তর: ২ বার। ৩-৩-৩ তিন ভাগ করুন; দুই ভাগ দুই পাল্লায়। যে দিক নামে, ভারীটি সেখানে; সমান হলে বাইরের ভাগে। এবার সেই তিনটির দুটি দুই পাল্লায়, একই যুক্তি।
কেন ১ বারে অসম্ভব: প্রতিটি মাপার তিনটি ফল (বাঁ ভারী, সমান, ডান ভারী), তাই h বার মাপা একটি ternary decision tree, যার leaf বড়জোর 3h. ৯টি সম্ভাবনার প্রতিটির আলাদা leaf লাগে, তাই 3h ≥ 9, h ≥ 2. মূল কৌশল: দুই ভাগে নয়, তিন ভাগে ভাগ করুন, কারণ পাল্লার উত্তর তিন রকম। রিভিশন: পর্ব ৬৫ Tree.
প্র ৩১২টি মুদ্রার একটি নকল, যা হয় ভারী নয় হালকা (কোনটি জানা নেই)। তিনবার মেপে নকলটি আর সেটি ভারী না হালকা বের করা যায় কি? যদি তিনটি মাপা আগে থেকেই ঠিক করে রাখতে হয়, তবুও? (কঠিন)
নিচের সীমা: সম্ভাব্য উত্তর 12 × 2 = 24টি, আর তিনবারে বড়জোর 3³ = 27টি ফল; 24 ≤ 27, তাই তিনবার যথেষ্ট হতে পারে, দুবার (9 < 24) কখনো নয়। পর্ব ৬৫-এ এই যুক্তি আছে।
একটি আগে-ঠিক-করা পরিকল্পনা: প্রতিটি মুদ্রাকে তিন অক্ষরের একটি “কোড” দিন, প্রতিটি অক্ষর +1 (বাঁ পাল্লা), −1 (ডান পাল্লা) বা 0 (বাইরে)। শর্ত তিনটি: (১) কোনো কোড শূন্য নয়; (২) কোনো দুটি কোড সমান বা পরস্পরের উল্টো নয়; (৩) প্রতিটি মাপায় বাঁয়ে ৪টি, ডানে ৪টি, যাতে নকল না থাকলে পাল্লা সমান থাকে। তখন নকলটি ভারী হলে তিন মাপার ফল ঠিক তার কোড, হালকা হলে কোডের উল্টো। ২৪টি পরিস্থিতির ফল সব আলাদা, তাই ফল দেখে মুদ্রা আর ভারী-হালকা দুটিই জানা যায়। (13টি “±” জোড়ার একটি, (1, 1, 1), বাদ রেখে বাকি ১২টি থেকে চিহ্ন বেছে শর্ত (৩) মেলানো হয়েছে।) নিচের ব্লক ২৪টি পরিস্থিতিই সত্যিকারের ওজন দিয়ে মাপে। রিভিশন: পর্ব ৬৫ Tree, পর্ব ৩১ গণনার মূল নিয়ম.
# 12 coins, one fake (heavier or lighter), 3 fixed weighings decided in advance.
# code[c][w] = +1: coin c on the left pan in weighing w, -1: right pan, 0: off.
code = {1: (-1, -1, 0), 2: (1, 1, -1), 3: (-1, 0, -1), 4: (-1, 0, 0),
5: (-1, 0, 1), 6: (1, -1, 1), 7: (1, -1, 0), 8: (1, -1, -1),
9: (0, 1, 1), 10: (0, 1, 0), 11: (0, 1, -1), 12: (0, 0, 1)}
for w in range(3):
left = [c for c in code if code[c][w] == 1]
right = [c for c in code if code[c][w] == -1]
print(f"weighing {w + 1}: left {left} right {right}")
def weigh(fake, delta): # delta = +1 heavy, -1 light
out = []
for w in range(3):
wt = {c: 10 + (delta if c == fake else 0) for c in code}
L = sum(wt[c] for c in code if code[c][w] == 1)
R = sum(wt[c] for c in code if code[c][w] == -1)
out.append((L > R) - (L < R)) # +1 left heavier, 0 balance, -1 right
return tuple(out)
seen = {}
for fake in code:
for delta in (1, -1):
seen[weigh(fake, delta)] = (fake, "heavy" if delta == 1 else "light")
print("scenarios:", 24, "| distinct outcomes:", len(seen),
"| possible outcomes 3^3 =", 3 ** 3)
print("outcome (1, 1, -1) means coin", seen[(1, 1, -1)])
print("outcome (0, -1, -1) means coin", seen[(0, -1, -1)])আসল আউটপুট (ব্রাউজারের Python):
weighing 1: left [2, 6, 7, 8] right [1, 3, 4, 5]
weighing 2: left [2, 9, 10, 11] right [1, 6, 7, 8]
weighing 3: left [5, 6, 9, 12] right [2, 3, 8, 11]
scenarios: 24 | distinct outcomes: 24 | possible outcomes 3^3 = 27
outcome (1, 1, -1) means coin (2, 'heavy')
outcome (0, -1, -1) means coin (9, 'light')
প্রতি মাপায় বাঁয়ে আর ডানে ৪টি করে মুদ্রা, আর ২৪টি পরিস্থিতি ২৪টি আলাদা ফল দেয়; ২৭টি সম্ভাব্য ফলের তিনটি কখনো আসে না (সব সমান, আর দুটি বাদ পড়া কোড)। ফল (1, 1, −1), অর্থাৎ বাঁ ভারী, বাঁ ভারী, ডান ভারী, মানে ২ নম্বর ভারী; (0, −1, −1) মানে ৯ নম্বর হালকা। ইন্টারভিউতে পরিকল্পনাটি মুখস্থ বলার দরকার নেই; সীমা (24 ≤ 27) আর তিন ভাগের ধারণা বলতে পারাই মূল।
প্র ৪বাজারঘরের অনুষ্ঠানে n ≥ 2 জন অতিথি, কেউ কেউ পরস্পরের সাথে হাত মেলালেন (একই জোড়া একবারই)। প্রমাণ করুন: (ক) অন্তত দুজন সমান সংখ্যক হাত মিলিয়েছেন; (খ) বিজোড় সংখ্যক হাত মেলানো অতিথির সংখ্যা জোড়। (মাঝারি)
অতিথি = vertex, হাত মেলানো = edge; একজনের হাত মেলানোর সংখ্যা তাঁর degree.
- (ক) degree-র সম্ভাব্য মান 0, 1, …, n − 1, মোট n টি। কিন্তু 0 আর n − 1 একসাথে থাকতে পারে না: কেউ সবার সাথে হাত মেলালে কেউ শূন্য নন। — case
- তাই বাস্তবে বড়জোর n − 1টি মান, অতিথি n জন; pigeonhole অনুযায়ী দুজনের degree সমান। ∎
- (খ) প্রতিটি হাত মেলানো দুজনের degree-তে ১ করে যোগ করে, তাই সব degree-র যোগফল 2E, জোড়। — handshake lemma
- জোড় degree-গুলোর যোগফল জোড়, তাই বিজোড় degree-গুলোর যোগফলও জোড়; বিজোড় সংখ্যার যোগফল জোড় হয় কেবল তাদের সংখ্যা জোড় হলে। ∎
রিভিশন: পর্ব ৩২ Pigeonhole principle, পর্ব ৫৭ গ্রাফ.
প্র ৫একটি দ্বীপে knight সবসময় সত্য বলেন, knave সবসময় মিথ্যা। A বললেন, “B একজন knave.” B বললেন, “A আর C একই ধরনের।” C বললেন, “A একজন knave.” কে কী? (মাঝারি)
প্রতিটি মানুষের জন্য একটি proposition নিন: A = “A knight”. Knight-এর কথা সত্য, knave-এর কথা মিথ্যা, তাই প্রতিটি কথা একটি biconditional: A ↔ ¬B, B ↔ (A ↔ C), C ↔ ¬A.
- ধরি A knave. তাহলে C ↔ ¬A থেকে C knight, আর A ↔ ¬B থেকে B knight.
- B knight হলে তাঁর কথা সত্য: A আর C একই ধরনের। কিন্তু A knave, C knight — বিরোধ। তাই A knight.
- A knight ⇒ B knave (A-র কথা) আর C knave (C ↔ ¬A).
- যাচাই: B knave, তাই “A আর C একই ধরনের” মিথ্যা হওয়া চাই; সত্যিই A knight, C knave, আলাদা। ∎
উত্তর: A knight, B আর C knave. আটটি সম্ভাবনার truth table পুরোটা দেখলেও একটিই সারি টেকে (নিচের ব্লক)। রিভিশন: পর্ব ০২ Propositional logic, পর্ব ০৭ কাজে যুক্তি.
from itertools import product
# Island puzzle: knights always tell the truth, knaves always lie.
# A: "B is a knave." B: "A and C are the same type." C: "A is a knave."
for A, B, C in product((True, False), repeat=3): # True = knight
said = {"A": not B, "B": A == C, "C": not A}
# a knight's statement is true, a knave's statement is false
if said["A"] == A and said["B"] == B and said["C"] == C:
print("A", "knight" if A else "knave", "| B", "knight" if B else "knave",
"| C", "knight" if C else "knave")আসল আউটপুট (ব্রাউজারের Python):
A knight | B knave | C knave
আটটি সারির একটিই সব শর্ত মেলায়, হাতের উত্তরের সঙ্গে একই। দ্বীপবাসীর সংখ্যা বাড়লে সারি 2n হারে বাড়ে; তখন SAT solver কাজে আসে (পর্ব ৭৭ SAT আর SMT solver).
প্র ৬পাঁচজন জলদস্যু (জ্যেষ্ঠতা ক্রমে ১ থেকে ৫) ১০০টি স্বর্ণমুদ্রা ভাগ করবেন। সবচেয়ে জ্যেষ্ঠ একটি ভাগ প্রস্তাব করেন; প্রস্তাবকসহ সবাই ভোট দেন। অন্তত অর্ধেক “হ্যাঁ” হলে ভাগ চূড়ান্ত, নইলে প্রস্তাবককে সাগরে ফেলা হয় আর পরের জন প্রস্তাব করেন। প্রত্যেকে আগে বাঁচতে চান, তারপর বেশি মুদ্রা, আর সমান হলে কাউকে ফেলে দিতে পছন্দ করেন। প্রথম প্রস্তাবক কী প্রস্তাব করবেন? (কঠিন)
এটি একটি বহুল প্রচলিত পুরোনো ধাঁধা, যার নিয়ম বইভেদে একটু আলাদা; উত্তর নিয়মের ওপর নির্ভর করে, তাই আগে নিয়ম জিজ্ঞেস করে নিন। কৌশল: backward induction, অর্থাৎ শেষ থেকে শুরু।
- ১ জন বাকি (৫ নম্বর): সব ১০০ তাঁর। ২ জন (৪, ৫): ৪ নিজের ভোটেই অর্ধেক, তাই (100, 0).
- ৩ জন (৩, ৪, ৫): ২টি ভোট লাগে। ৫ নম্বর পরের ধাপে ০ পাবেন, তাই ১টি মুদ্রায় কেনা যায়: (99, 0, 1).
- ৪ জন (২–৫): ২টি ভোট; পরের ধাপে ৪ নম্বর ০ পান, তাঁকে ১: (99, 0, 1, 0).
- ৫ জন (১–৫): ৩টি ভোট, অর্থাৎ নিজের বাইরে আরও ২টি। পরের ধাপে ৩ আর ৫ নম্বর ০ পান; প্রত্যেককে ১টি করে দিলেই তাঁরা ভালো থাকেন: (98, 0, 1, 0, 1). ∎
প্রতিটি ধাপ আগের ধাপের ফল ব্যবহার করে, যা strong induction-এর মতো। নিয়ম বদলালে (যেমন “অর্ধেকের বেশি” লাগলে) উত্তর বদলায়; ইন্টারভিউতে এটি বলে দেওয়াই পরিপক্বতা। রিভিশন: পর্ব ১১ Strong induction.
from itertools import combinations
# 1) 100 bulbs; person k toggles every bulb whose number k divides
on = [b for b in range(1, 101) if sum(b % k == 0 for k in range(1, b + 1)) % 2]
print("bulbs left on:", on)
# 2) party: every simple graph on n vertices has two equal degrees
for n in range(2, 7):
pairs = list(combinations(range(n), 2)); ok = True
for mask in range(1 << len(pairs)):
deg = [0] * n
for i, (a, b) in enumerate(pairs):
if mask >> i & 1: deg[a] += 1; deg[b] += 1
ok = ok and len(set(deg)) < n
print(f"n={n}: {1 << len(pairs):5d} graphs, repeated degree always: {ok}")
# 3) pirates by backward induction (at least half the votes must say yes)
def split(n, coins=100):
if n == 1: return [coins]
later = split(n - 1, coins) # what the others get if the proposer dies
need = (n + 1) // 2 - 1 # yes-votes needed besides the proposer
cheap = sorted(range(n - 1), key=lambda j: later[j])[:need]
plan = [0] * n
for j in cheap:
plan[j + 1] = later[j] + 1 # one coin more than they would get later
plan[0] = coins - sum(plan)
return plan
for n in range(1, 6):
print(n, "pirates:", split(n))আসল আউটপুট (ব্রাউজারের Python):
bulbs left on: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
n=2: 2 graphs, repeated degree always: True
n=3: 8 graphs, repeated degree always: True
n=4: 64 graphs, repeated degree always: True
n=5: 1024 graphs, repeated degree always: True
n=6: 32768 graphs, repeated degree always: True
1 pirates: [100]
2 pirates: [100, 0]
3 pirates: [99, 0, 1]
4 pirates: [99, 0, 1, 0]
5 pirates: [98, 0, 1, 0, 1]
বাল্বের তালিকা ঠিক দশটি পূর্ণবর্গ, অংশ ১-এর stepper-এর সাথে মেলে। হাত মেলানোর দাবি n = 6 পর্যন্ত প্রতিটি সম্ভাব্য graph-এ (n = 6-এ 215 = 32,768টি) সত্য; এটি প্রমাণ নয়; প্রমাণ আছে প্র ৪-এ। জলদস্যুর ভাগ ধাপে ধাপে হাতের হিসাবের সঙ্গে মেলে: শেষ সারি (98, 0, 1, 0, 1).
৩ · গণনা আর সম্ভাবনার brain-teaser
রিভিশন: পর্ব ৩৩ Permutation আর combination, পর্ব ৩৯ শর্তসাপেক্ষ সম্ভাবনা, পর্ব ৪০ Random variable আর প্রত্যাশা, পর্ব ৪২ Birthday paradox
সম্ভাবনার প্রশ্নে প্রথম বাক্যটি প্রায় সবসময় একই: “ধরে নিচ্ছি প্রতিটি ফল সমান সম্ভাবনায় আর স্বাধীনভাবে আসে।” দ্বিতীয় অস্ত্র linearity of expectation: E[X + Y] = E[X] + E[Y], X আর Y স্বাধীন না হলেও।
প্র ৭একটি ছক্কা বারবার ছোড়া হচ্ছে। প্রথম ৬ আসতে গড়ে কতবার ছুড়তে হবে? (সহজ)
উত্তর: ৬. প্রতিবার সফলতার সম্ভাবনা p = 1/6, আর প্রথম সফলতা পর্যন্ত চেষ্টার সংখ্যা geometric distribution মানে; E = 1/p. এক লাইনের প্রমাণ: E = 1 + (1 − p)·E, কারণ একবার ছুড়তেই হয়, আর ব্যর্থ হলে (সম্ভাবনা 1 − p) একই অবস্থা থেকে আবার শুরু। সমাধান E = 1/p. রিভিশন: পর্ব ৪০ প্রত্যাশা.
প্র ৮বাজারঘরের প্রতিটি চিপসের প্যাকেটে ৬ রকম ক্রিকেটার-কার্ডের একটি, প্রতিটি সমান সম্ভাবনায়। সব ৬ রকম জোগাড় করতে গড়ে কয়টি প্যাকেট লাগবে? (মাঝারি)
এটি coupon collector সমস্যা। i রকম হাতে থাকলে নতুন একটি পাওয়ার সম্ভাবনা (6 − i)/6, তাই সেই ধাপে গড় অপেক্ষা 6/(6 − i) (প্র ৭-এর সূত্র)। মোট অপেক্ষা ধাপগুলোর যোগফল, আর linearity অনুযায়ী গড়ও যোগ হয়:
E = 6/6 + 6/5 + 6/4 + 6/3 + 6/2 + 6/1 = 6 · H6 = 6 · 49/20 = 14.7
সাধারণভাবে n রকমে n·Hn ≈ n ln n, যেখানে Hn = 1 + 1/2 + … + 1/n একটি harmonic number. শেষের কয়েকটি রকমই বেশি সময় নেয়: শেষ কার্ডটির জন্যই গড়ে ৬টি প্যাকেট। রিভিশন: পর্ব ৪০ প্রত্যাশা.
প্র ৯৩৬৫ দিনের বছরে জন্মদিন সমান সম্ভাবনায় আর স্বাধীন ধরলে, কমপক্ষে কতজন হলে দুজনের জন্মদিন এক হওয়ার সম্ভাবনা অর্ধেক ছাড়ায়? উত্তরটি অবাক করে কেন? (মাঝারি)
উত্তর: ২৩ জন, সম্ভাবনা প্রায় ০.৫০৭. পরিপূরক নিন: সবার জন্মদিন আলাদা হওয়ার সম্ভাবনা (365/365)(364/365)…((365 − n + 1)/365); এটি ১ থেকে বিয়োগ করলে উত্তর। অবাক লাগে, কারণ আমরা “আমার সাথে কারও মিল” ভাবি, অথচ গোনা হয় যেকোনো জোড়া: ২৩ জনে C(23, 2) = 253টি জোড়া। এর প্রয়োগ hash collision: k-বিটের hash-এ মোটামুটি 2k/2টি মান নিলেই মিলের সম্ভাবনা উল্লেখযোগ্য; এটিই birthday paradox-এর শিক্ষা। রিভিশন: পর্ব ৪২ Birthday paradox আর hash collision.
প্র ১০একটি ন্যায্য মুদ্রা ছোড়া হচ্ছে। “HH” (পরপর দুটি head) আসতে গড়ে কতবার, আর “HT” আসতে কতবার? দুটি কি সমান? (কঠিন)
উত্তর: HH-এ ৬, HT-তে ৪ — সমান নয়। অবস্থা ধরে প্রত্যাশিত মান-এর সমীকরণ লিখুন। E0 = শুরু থেকে বাকি গড়, E1 = শেষে একটি H আছে এমন অবস্থা থেকে।
- HH: E0 = 1 + ½E1 + ½E0; E1 = 1 + ½·0 + ½E0 (T এলে আবার শুরু)। সমাধান E0 = 6.
- HT: E0 = 1 + ½E1 + ½E0; E1 = 1 + ½E1 + ½·0 (H এলে H-এই থাকি, শুরুতে ফিরি না)। তাই E1 = 2, E0 = 4. ∎
পার্থক্যের কারণ: HH-এর মাঝপথে T এলে পুরো অগ্রগতি হারায়, HT-র মাঝপথে H এলে কিছুই হারায় না। একই পদ্ধতিতে ছক্কায় পরপর দুটি ৬: 6 + 6² = 42. রিভিশন: পর্ব ৩৯ শর্তসাপেক্ষ সম্ভাবনা, পর্ব ৪০ প্রত্যাশা.
প্র ১১৮ × ৮ দাবার বোর্ডে (যেকোনো আকারের) কতগুলো আয়তক্ষেত্র আছে? (মাঝারি)
উত্তর: C(9, 2)² = 36² = 1296. বোর্ডে ৯টি খাড়া আর ৯টি আড়াআড়ি রেখা। প্রতিটি আয়তক্ষেত্র ঠিক দুটি খাড়া আর দুটি আড়াআড়ি রেখা দিয়ে ঘেরা, আর উল্টোভাবে প্রতিটি এমন বাছাই একটি আয়তক্ষেত্র দেয়: এটি একটি bijection. তাই গুণের নিয়মে C(9, 2) × C(9, 2). ঘর ধরে গুনতে গেলে জট পাকে; রেখা ধরে গোনাই কৌশল। রিভিশন: পর্ব ৩৩ Permutation আর combination, পর্ব ৩৭ গণনার কৌশল.
from fractions import Fraction as F
from math import prod, comb
import random
# coupon collector: n kinds, expected draws = n * H_n
for n in (6, 50):
e = n * sum(F(1, k) for k in range(1, n + 1))
print(f"coupon collector n={n}: about {float(e):.2f} draws")
# birthday: smallest group with P(shared day) >= 1/2 (365 days, uniform)
n = 1
while 1 - prod(F(365 - k, 365) for k in range(n)) < F(1, 2):
n += 1
print("birthday:", n, "people, P =", round(float(1 - prod(F(365 - k, 365) for k in range(n))), 4))
# expected rolls until a pattern appears: exact formula and a seeded simulation
def wait(pattern, sides, trials=40000, seed=7):
rng = random.Random(seed); total = 0
for _ in range(trials):
last = ""; t = 0
while not last.endswith(pattern):
last += str(rng.randrange(sides)); t += 1
total += t
return total / trials
print("first 6 on a die: exact 6, simulated", round(wait("5", 6), 2))
print("HH with a coin: exact 6, simulated", round(wait("11", 2), 2))
print("HT with a coin: exact 4, simulated", round(wait("10", 2), 2))
print("two 6s in a row: exact", 6 + 6 ** 2, "simulated", round(wait("55", 6, 20000), 1))
# rectangles on an 8 x 8 board: choose 2 of 9 vertical and 2 of 9 horizontal lines
print("rectangles on 8x8:", sum(1 for a in range(9) for b in range(a + 1, 9) for c in range(9) for d in range(c + 1, 9)), comb(9, 2) ** 2)আসল আউটপুট (ব্রাউজারের Python):
coupon collector n=6: about 14.70 draws
coupon collector n=50: about 224.96 draws
birthday: 23 people, P = 0.5073
first 6 on a die: exact 6, simulated 6.02
HH with a coin: exact 6, simulated 5.99
HT with a coin: exact 4, simulated 4.01
two 6s in a row: exact 42 simulated 42.3
rectangles on 8x8: 1296 1296
৬ রকম কার্ডে ঠিক 147/10 = 14.7 প্যাকেট (Fraction দিয়ে হিসাব), ৫০ রকমে প্রায় ২২৫. জন্মদিনে ২৩ জনে সম্ভাবনা ০.৫০৭৩. Seed দেওয়া simulation-এ ৬-এর অপেক্ষা ৬.০২, HH ৫.৯৯, HT ৪.০১, পরপর দুটি ৬ ৪২.৩: সূত্রের ৬, ৬, ৪, ৪২-এর কাছে, যা সমীকরণের সমাধানকে সমর্থন করে (simulation প্রমাণ নয়)। আয়তক্ষেত্র brute force-এ ১২৯৬, সূত্রের সমান।
৪ · Bit trick আর modular arithmetic — কোডিং ইন্টারভিউর সংখ্যাতত্ত্ব
রিভিশন: পর্ব ২০ বিভাজ্যতা আর মৌলিক সংখ্যা, পর্ব ২২ Modular arithmetic, পর্ব ২৪ Fermat আর Euler, পর্ব ৭৭ SAT আর SMT solver; কোডে: DSA কোর্সের পর্ব ৬২
প্র ১২x একটি ধনাত্মক পূর্ণসংখ্যা। x & (x - 1) কী করে? এটি দিয়ে “x কি 2-এর ঘাত?” কীভাবে পরীক্ষা করবেন, আর x = 0 নিয়ে সাবধানতা কী? (সহজ)
x-কে binary-তে লিখুন: শেষ অংশটি একটি 1, তারপর k টি 0. x − 1-এ সেই 1 হয়ে যায় 0, আর পরের k টি 0 হয়ে যায় 1; ওপরের অংশ একই থাকে। AND নিলে ওপরের অংশ টিকে থাকে, নিচের সব শূন্য: সবচেয়ে ডানের 1-বিটটি মুছে যায়। উদাহরণ: 40 = 101000, 40 & 39 = 100000 = 32.
2-এর ঘাতে ঠিক একটি 1-বিট, তাই x > 0 আর x & (x - 1) == 0 ⇔ x একটি 2-এর ঘাত। শর্ত x > 0 জরুরি: x = 0-তেও 0 & -1 == 0, অথচ 0 কোনো ঘাত নয়। একই কৌশল বারবার চালালে 1-বিট গোনা যায় (প্রতিটি ধাপে একটি বিট মোছে)। পর্ব ৭৭-এ Z3 দিয়ে এই নিয়ম সব 32-বিট মানে প্রমাণ করা হয়েছে। রিভিশন: পর্ব ৭৭ SAT আর SMT solver.
প্র ১৩একটি তালিকায় প্রতিটি সংখ্যা ঠিক দুবার আছে, শুধু একটি একবার। O(n) সময় আর O(1) অতিরিক্ত মেমরিতে সেটি খুঁজুন, আর কেন কাজ করে তা প্রমাণ করুন। (সহজ)
সবগুলোর XOR নিন। কারণ: XOR (বিট ধরে ২ দিয়ে ভাগশেষের যোগ) associative আর commutative, a ⊕ a = 0, a ⊕ 0 = a. তাই ক্রম বদলে জোড়াগুলো পাশাপাশি আনা যায়; প্রতিটি জোড়া 0 হয়, বাকি থাকে একক সংখ্যাটি। ∎ বীজগণিতের ভাষায়, k-বিট সংখ্যা XOR-এর অধীনে একটি group, যেখানে প্রতিটি উপাদান নিজেই নিজের inverse. রিভিশন: পর্ব ৭০ Group.
প্র ১৪0 থেকে 2n − 1 পর্যন্ত সব n-বিট সংখ্যা এমন ক্রমে সাজান, যাতে পাশাপাশি দুটির মধ্যে ঠিক একটি বিট আলাদা। (মাঝারি)
এটি Gray code: g(i) = i ⊕ (i >> 1). ৩ বিটে: 000, 001, 011, 010, 110, 111, 101, 100. Recursive গঠন: n − 1 বিটের তালিকা, তারপর সেটি উল্টো ক্রমে আর সামনে একটি 1. সংযোগস্থলে শুধু সামনের বিট বদলায়, আর দুই অর্ধেকের ভেতরে induction অনুযায়ী এক বিট। Graph-এর ভাষায় এটি n-মাত্রার cube-এ একটি Hamilton path. রিভিশন: পর্ব ৩৬ Permutation আর combination তৈরি, পর্ব ৬০ Euler আর Hamilton path.
N = 1 << 12 # check every x from 1 to 4095
def popcount_kernighan(x):
steps = 0
while x:
x &= x - 1 # clears the lowest 1-bit
steps += 1
return steps
print("x & (x-1) == x - lowbit:", all(x & (x - 1) == x - (x & -x) for x in range(1, N)))
print("loop count == number of 1s:", all(popcount_kernighan(x) == bin(x).count("1") for x in range(N)))
pow2 = [x for x in range(1, N) if x & (x - 1) == 0]
print("x & (x-1) == 0 picks:", pow2[:6], "...", len(pow2), "values")
print("40 =", bin(40), "-> 40 & 39 =", bin(40 & 39), "=", 40 & 39)
# the unique number: every value appears twice except one
data = [7, 3, 9, 3, 7, 4, 9]
acc = 0
for v in data:
acc ^= v
print("unique by XOR:", acc)
# Gray code: neighbours differ in exactly one bit
g = [i ^ (i >> 1) for i in range(16)]
print("Gray 0..7:", [format(v, "03b") for v in g[:8]])
print("one-bit steps:", all(bin(g[i] ^ g[i + 1]).count("1") == 1 for i in range(15)))আসল আউটপুট (ব্রাউজারের Python):
x & (x-1) == x - lowbit: True
loop count == number of 1s: True
x & (x-1) == 0 picks: [1, 2, 4, 8, 16, 32] ... 12 values
40 = 0b101000 -> 40 & 39 = 0b100000 = 32
unique by XOR: 4
Gray 0..7: ['000', '001', '011', '010', '110', '111', '101', '100']
one-bit steps: True
১ থেকে ৪০৯৫ পর্যন্ত প্রতিটি x-এ নিয়মটি খাটে, আর x & (x-1) == 0 ঠিক ১২টি মান বাছে: 1, 2, 4, …, 2048. XOR তালিকা থেকে 4 বের করে, আর Gray code-এর পাশাপাশি প্রতিটি জোড়ায় এক বিটের পার্থক্য।
প্র ১৫72026-এর একক স্থানের অঙ্ক কত? (সহজ)
উত্তর: ৯. একক অঙ্ক মানে mod 10. 7¹ ≡ 7, 7² ≡ 9, 7³ ≡ 3, 7⁴ ≡ 1 (mod 10), তাই ঘাতগুলো ৪-এর চক্রে ঘোরে। 2026 = 4 · 506 + 2, তাই 72026 ≡ (7⁴)506 · 7² ≡ 1 · 9 ≡ 9. বড় ঘাতে কোডে pow(7, 2026, 10) বা repeated squaring, যা log₂ 2026-এর কাছাকাছি গুণে কাজ সারে। রিভিশন: পর্ব ২২ Modular arithmetic.
প্র ১৬Contest-এ বলা হলো “উত্তর mod 109 + 7 দিন”, আর হিসাবে n!/(r!(n − r)!) আছে। ভাগ কীভাবে করবেন, আর কোন শর্তে পদ্ধতিটি ঠিক? (মাঝারি)
Modular হিসাবে সরাসরি ভাগ নেই; ভাগের বদলে modular inverse দিয়ে গুণ। p = 109 + 7 মৌলিক, আর p ∤ a হলে Fermat-এর ছোট উপপাদ্য ap−1 ≡ 1 দেয়, তাই a−1 ≡ ap−2 (mod p). তাই C(n, r) ≡ n! · (r!)p−2 · ((n − r)!)p−2 (mod p).
শর্ত: p মৌলিক, আর r! ও (n − r)! p দিয়ে বিভাজ্য নয়, যা n < p হলে নিশ্চিত। Modulus মৌলিক না হলে Fermat চলে না; তখন extended Euclid, আর gcd(a, m) = 1 হলেই কেবল inverse আছে। আরেকটি ফাঁদ: C বা Java-তে ঋণাত্মক সংখ্যার % ঋণাত্মক হতে পারে, তাই ((a % m) + m) % m লিখুন। রিভিশন: পর্ব ২১ GCD আর Euclid, পর্ব ২৪ Fermat আর Euler.
প্র ১৭100!-এর শেষে কয়টি শূন্য? (মাঝারি)
উত্তর: ২৪. প্রতিটি শেষের শূন্য একটি 10 = 2 × 5 উৎপাদক। 100!-এ ২-এর উৎপাদক ৫-এর চেয়ে অনেক বেশি, তাই শূন্যের সংখ্যা = ৫-এর ঘাত। Legendre-এর সূত্র: ⌊100/5⌋ + ⌊100/25⌋ + ⌊100/125⌋ = 20 + 4 + 0 = 24. ২৫, ৫০, ৭৫, ১০০ প্রত্যেকে দুটি ৫ দেয়; এগুলো ভুলে গেলে উত্তর ২০ আসে, যা সবচেয়ে পরিচিত ভুল। রিভিশন: পর্ব ২০ বিভাজ্যতা আর মৌলিক সংখ্যা.
from math import comb, factorial
P = 10**9 + 7 # a prime used by many judges
# last digit of 7^2026: powers of 7 mod 10 repeat with period 4
print("7^k mod 10, k=1..4:", [pow(7, k, 10) for k in range(1, 5)],
"| 2026 mod 4 =", 2026 % 4, "| 7^2026 mod 10 =", pow(7, 2026, 10))
# modular inverse: Fermat (a^(p-2)) and Python's built-in pow(a, -1, p)
a = 2
print("inverse of 2 mod P:", pow(a, P - 2, P), pow(a, -1, P), "check:", a * pow(a, -1, P) % P)
# C(n, r) mod P from factorials and inverse factorials
n = 1000
fact = [1] * (n + 1)
for i in range(1, n + 1):
fact[i] = fact[i - 1] * i % P
def C(n, r):
return fact[n] * pow(fact[r], P - 2, P) * pow(fact[n - r], P - 2, P) % P
print("C(1000, 500) mod P:", C(1000, 500), "matches exact:", C(1000, 500) == comb(1000, 500) % P)
# trailing zeros of 100!: Legendre's count of 5s
z, q = 0, 5
while q <= 100:
z += 100 // q; q *= 5
s = str(factorial(100))
print("zeros of 100! by formula:", z, "| by counting:", len(s) - len(s.rstrip("0")))
# negative numbers: Python's % is never negative for a positive modulus
print("-7 % 3 =", -7 % 3, "(C or Java would give -1)")আসল আউটপুট (ব্রাউজারের Python):
7^k mod 10, k=1..4: [7, 9, 3, 1] | 2026 mod 4 = 2 | 7^2026 mod 10 = 9
inverse of 2 mod P: 500000004 500000004 check: 1
C(1000, 500) mod P: 159835829 matches exact: True
zeros of 100! by formula: 24 | by counting: 24
-7 % 3 = 2 (C or Java would give -1)
৭-এর ঘাতের চক্র 7, 9, 3, 1, আর ঘাত 2026-এ ৯. ২-এর inverse 500000004, দুই পদ্ধতিতেই, আর 2 × 500000004 ≡ 1. Factorial দিয়ে C(1000, 500) mod p পুরো সংখ্যা থেকে নেওয়া মানের সমান। 100!-এর শূন্য সূত্রে আর গুনে দুভাবেই ২৪. Python-এর % ঋণাত্মক দেয় না, কিন্তু অন্য ভাষায় সাবধান।
৫ · Graph আর tree-র ইন্টারভিউ প্রশ্ন
রিভিশন: পর্ব ৫৬ Partial order আর topological sort, পর্ব ৬১ Shortest path-এর গণিত, পর্ব ৬৩ Graph coloring, পর্ব ৬৫ Tree, পর্ব ৬৭ Spanning tree; কোডে: DSA কোর্সের পর্ব ৪০, পর্ব ৪২, পর্ব ৪৩
প্র ১৮n টি vertex আর কিছু edge-এর একটি undirected graph দেওয়া। এটি tree কি না কীভাবে বলবেন? আর edge একে একে আসতে থাকলে প্রথম cycle কীভাবে ধরবেন? (সহজ)
Tree = connected আর cycle নেই। উপপাদ্য: n টি vertex-এর graph-এ এই তিনটির যেকোনো দুটি সত্য হলে তৃতীয়টিও সত্য — connected, acyclic, ঠিক n − 1টি edge. তাই পরীক্ষা: edge গুনুন (n − 1 কি?), তারপর একবার BFS বা DFS-এ সব vertex পৌঁছায় কি না।
Edge একে একে এলে union-find: প্রতিটি component একটি সেট। নতুন edge (u, v)-এর দুই প্রান্ত আগে থেকেই একই সেটে থাকলে তাদের মধ্যে আগেই একটি পথ আছে, তাই এই edge একটি cycle বন্ধ করে; না থাকলে দুই সেট জুড়ে দিন। রিভিশন: পর্ব ৬৫ Tree; কোডে DSA কোর্সের পর্ব ৩৩.
প্র ১৯বাজারঘরের রাইডারদের দুই শিফটে ভাগ করতে হবে, যাতে “একসাথে কাজ করা যায় না” এমন কোনো জোড়া একই শিফটে না পড়েন। কখন সম্ভব, আর কীভাবে পরীক্ষা করবেন? (মাঝারি)
রাইডার = vertex, দ্বন্দ্ব = edge; প্রশ্নটি হলো graph-টি bipartite কি না। উপপাদ্য: graph bipartite ⇔ কোনো বিজোড় দৈর্ঘ্যের cycle নেই।
- (⇒) bipartite হলে প্রতিটি edge দুই দলের মধ্যে যায়, তাই একটি cycle-এ দল পালাক্রমে বদলায়; শুরুর দলে ফিরতে জোড় সংখ্যক ধাপ লাগে।
- (⇐) প্রতিটি component-এ একটি vertex থেকে BFS চালিয়ে দূরত্ব জোড় হলে রং ০, বিজোড় হলে ১ দিন। একই রঙের দুটি vertex-এর মধ্যে edge থাকলে, দুজনের BFS পথ আর সেই edge মিলে একটি বিজোড় দৈর্ঘ্যের closed walk হয়, যার ভেতরে একটি বিজোড় cycle থাকে; অনুমানের সাথে বিরোধ। ∎
Algorithm সরাসরি প্রমাণ থেকে আসে: BFS-এ রং দিন, বিরোধ পেলে থামুন। রিভিশন: পর্ব ৬৩ Graph coloring.
প্র ২০কিছু কাজের মধ্যে “A আগে, তারপর B” ধরনের শর্ত আছে। সব কাজ শর্ত মেনে একটি ক্রমে সাজানো কখন সম্ভব? প্রমাণসহ বলুন। (মাঝারি)
শর্তগুলো একটি directed graph. Topological order আছে ⇔ graph-টি DAG (directed cycle নেই)। Cycle থাকলে সেই cycle-এর কোনো কাজই প্রথমে আসতে পারে না। উল্টো দিকের মূল lemma: প্রতিটি সসীম DAG-এ অন্তত একটি vertex-এ কোনো arc ঢোকে না। প্রমাণ: না থাকলে প্রতিটি vertex থেকে একটি arc উল্টো দিকে ধরে পিছিয়ে যেতে পারতেন অসীম সময় ধরে; vertex সসীম, তাই pigeonhole অনুযায়ী কোনো vertex ফিরে আসে, অর্থাৎ cycle, বিরোধ। সেই vertex প্রথমে বসিয়ে মুছে দিন; বাকিটাও DAG, তাই induction-এ বাকি ক্রম। এটিই Kahn-এর algorithm, আর কাজ আটকে গেলে বাকি vertex-গুলো cycle-এর খবর দেয়। রিভিশন: পর্ব ৫৬ Partial order আর topological sort.
from collections import deque
def two_colour(n, edges): # BFS; colours, or an edge inside one colour
adj = {v: [] for v in range(n)}
for a, b in edges: adj[a].append(b); adj[b].append(a)
col = {}
for s in range(n):
if s in col: continue
col[s] = 0; q = deque([s])
while q:
u = q.popleft()
for w in adj[u]:
if w not in col: col[w] = 1 - col[u]; q.append(w)
elif col[w] == col[u]: return f"not bipartite (edge {u}-{w} inside one colour)"
return "bipartite, one side " + str([v for v in range(n) if col[v] == 0])
print("C4:", two_colour(4, [(0, 1), (1, 2), (2, 3), (3, 0)]))
print("C5:", two_colour(5, [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]))
def kahn(nodes, arcs): # topological order, or the nodes left on a cycle
indeg = {v: 0 for v in nodes}
for a, b in arcs: indeg[b] += 1
q = deque(v for v in nodes if indeg[v] == 0); order = []
while q:
u = q.popleft(); order.append(u)
for a, b in arcs:
if a == u:
indeg[b] -= 1
if indeg[b] == 0: q.append(b)
return order if len(order) == len(nodes) else f"cycle among {sorted(set(nodes) - set(order))}"
jobs = ["order", "pay", "pack", "ship", "deliver"]
print(kahn(jobs, [("order", "pay"), ("pay", "pack"), ("pack", "ship"), ("ship", "deliver")]))
print(kahn(jobs, [("order", "pay"), ("pay", "pack"), ("pack", "ship"), ("ship", "pay")]))আসল আউটপুট (ব্রাউজারের Python):
C4: bipartite, one side [0, 2]
C5: not bipartite (edge 2-3 inside one colour)
['order', 'pay', 'pack', 'ship', 'deliver']
cycle among ['pack', 'pay', 'ship']
৪ দৈর্ঘ্যের cycle দুই রঙে ভাগ হয়, ৫ দৈর্ঘ্যেরটিতে BFS একটি edge-এর দুই প্রান্তে একই রং পায়: বিজোড় cycle-এর চিহ্ন। অর্ডারের কাজগুলোর শর্তে একটি পরিষ্কার ক্রম আসে; “ship-এর পরে আবার pay” শর্ত যোগ করলে pay, pack, ship একটি cycle-এ আটকে যায়, আর Kahn তাদের নাম জানায়।
প্র ২১একটি directed graph-এ arc A→B (2), A→C (5), C→B (−4) আর B→D (1) আছে। A থেকে Dijkstra চালালে কী হয়? ঠিক উত্তর কোথা থেকে পাবেন? (মাঝারি)
Dijkstra B-কে আগে বের করে (দূরত্ব 2) আর “চূড়ান্ত” ধরে; পরে C (5) থেকে C→B দিয়ে 1 পাওয়া যেত, কিন্তু B তখন বন্ধ। তাই B = 2 আর D = 3, যেখানে আসল উত্তর B = 1, D = 2. Dijkstra-র সঠিকতার প্রমাণের মূল ধাপ: “যে vertex বের হলো, তার চেয়ে ছোট পথ পরে আসতে পারে না, কারণ পরের পথ শুধু লম্বা হয়” — এটি সব weight ≥ 0 ধরে নেয়। একটি ঋণাত্মক edge থাকলেই ধাপটি ভাঙে। Bellman-Ford প্রতিটি arc |V| − 1 বার relax করে ঠিক উত্তর দেয়, যদি A থেকে পৌঁছানো যায় এমন কোনো ঋণাত্মক cycle না থাকে। রিভিশন: পর্ব ৬১ Shortest path-এর গণিত; কোডে DSA কোর্সের পর্ব ৪৪.
প্র ২২একটি weighted undirected graph-এর প্রতিটি edge-এর weight-এ একই ধনাত্মক সংখ্যা c যোগ করা হলো। (ক) Minimum spanning tree কি বদলাতে পারে? (খ) দুটি vertex-এর মধ্যে shortest path? (কঠিন)
(ক) না। প্রতিটি spanning tree-তে ঠিক n − 1টি edge, তাই প্রতিটির মোট weight ঠিক (n − 1)c বাড়ে; সবচেয়ে ছোটটি সবচেয়ে ছোটই থাকে। আরেক দিক থেকে: Kruskal শুধু edge-গুলোর ক্রম দেখে, আর c যোগে ক্রম বদলায় না। Cut property-ও একই কথা বলে।
(খ) হ্যাঁ, বদলাতে পারে। পথে edge-এর সংখ্যা আলাদা হতে পারে, আর k টি edge-এর পথ kc বাড়ে। উদাহরণ: A–C সরাসরি 3, A–B–C পথে 1 + 1 = 2; সবচেয়ে ছোট A–B–C. c = 2 যোগ করলে সরাসরি 5, অন্যটি 3 + 3 = 6; এখন সরাসরি পথই ছোট। শিক্ষা: MST আর shortest path আলাদা প্রশ্ন; একটির ধর্ম অন্যটিতে ধরে নেবেন না। রিভিশন: পর্ব ৬৭ Spanning tree, পর্ব ৬১ Shortest path.
import heapq
# directed graph with one negative edge: A->B 2, A->C 5, C->B -4, B->D 1
arcs = [("A", "B", 2), ("A", "C", 5), ("C", "B", -4), ("B", "D", 1)]
nodes = ["A", "B", "C", "D"]
def dijkstra(src): # textbook version: a vertex is final when popped
dist = {v: float("inf") for v in nodes}; dist[src] = 0
done = set(); pq = [(0, src)]
while pq:
d, u = heapq.heappop(pq)
if u in done: continue
done.add(u)
for a, b, w in arcs:
if a == u and b not in done and d + w < dist[b]:
dist[b] = d + w; heapq.heappush(pq, (dist[b], b))
return dist
def bellman_ford(src): # relax every arc |V| - 1 times
dist = {v: float("inf") for v in nodes}; dist[src] = 0
for _ in range(len(nodes) - 1):
for a, b, w in arcs:
if dist[a] + w < dist[b]:
dist[b] = dist[a] + w
return dist
print("Dijkstra :", dijkstra("A"))
print("Bellman-Ford:", bellman_ford("A"))
# add c = 2 to every weight: direct A-C (3) vs path A-B-C (1 + 1)
print("before:", min(3, 1 + 1), "| after +2:", min(3 + 2, (1 + 2) + (1 + 2)))আসল আউটপুট (ব্রাউজারের Python):
Dijkstra : {'A': 0, 'B': 2, 'C': 5, 'D': 3}
Bellman-Ford: {'A': 0, 'B': 1, 'C': 5, 'D': 2}
before: 2 | after +2: 5
Dijkstra B = 2, D = 3 দেয়, Bellman-Ford ঠিক মান B = 1, D = 2. শেষ লাইন প্র ২২-এর আলাদা ছোট উদাহরণ: আগে সবচেয়ে ছোট দূরত্ব ২ (A–B–C), সব weight-এ ২ যোগের পরে ৫, যা সরাসরি পথ; অর্থাৎ পথ বদলে গেছে।
৬ · Recurrence আর DP দিয়ে গণনা — সিঁড়ি, tiling আর Catalan
রিভিশন: পর্ব ৪৪ Recurrence দিয়ে মডেল, পর্ব ৪৫ Linear recurrence সমাধান, পর্ব ৫০ বিশেষ সংখ্যা — Catalan; কোডে: DSA কোর্সের পর্ব ৫২
DP-র গণনার প্রশ্নে একটিই প্রশ্ন জিজ্ঞেস করুন: “শেষ ধাপটি কী হতে পারে?” প্রতিটি সম্ভাব্য শেষ ধাপ আলাদা দল দেয়, দলগুলো আলাদা (যোগের নিয়ম), আর প্রতিটি দলের বাকিটা একটি ছোট একই রকম সমস্যা। এভাবেই recurrence আসে, আর ছোট মানগুলো টেবিলে রাখাই dynamic programming.
প্র ২৩বাজারঘরের গুদামে ১০ ধাপের সিঁড়ি। একবারে ১ বা ২ ধাপ ওঠা যায়। কতভাবে উপরে ওঠা যায়? (সহজ)
an = n ধাপে ওঠার উপায়। শেষ পা ১ ধাপের হলে আগে n − 1 ধাপ, ২ ধাপের হলে n − 2: তাই an = an−1 + an−2, a1 = 1, a2 = 2. টেবিল: 1, 2, 3, 5, 8, 13, 21, 34, 55, 89. এটি Fibonacci, এক ঘর সরানো। দুটি basis লাগে, কারণ recurrence দুই ধাপ পেছনে যায়। Recursion সরাসরি লিখলে একই মান বারবার হিসাব হয় আর সময় সূচকীয়; টেবিলে রাখলে O(n). রিভিশন: পর্ব ৪৪ Recurrence দিয়ে মডেল.
| n | |
| শেষ পা | |
| a(n) | |
| টেবিল |
- Basis ১. এক ধাপের সিঁড়িতে একটিই উপায়: একবার ১ ধাপ।
- Basis ২. দুই ধাপে দুটি উপায়: ১ + ১, বা একবারে ২. Recurrence দুই ধাপ পেছনে যায়, তাই দুটি basis লাগে।
- n = 3. শেষ পা ১ হলে আগে ২ ধাপ (২ উপায়), শেষ পা ২ হলে আগে ১ ধাপ (১ উপায়): ২ + ১ = ৩.
- n = 4. একই যুক্তি: a(3) + a(2) = ৩ + ২ = ৫. টেবিলের আগের দুটি ঘরই যথেষ্ট; কিছু আবার হিসাব করতে হয় না।
- n = 5. ৫ + ৩ = ৮. দুটি দল আলাদা, কারণ শেষ পা ভিন্ন; তাই যোগের নিয়ম খাটে, কোনো উপায় দুবার গোনা হয় না।
- n = 6. ৮ + ৫ = ১৩. এভাবে n = 10-এ ৮৯; নিচের ব্লক brute force tiling দিয়েও একই তালিকা দেয়।
প্র ২৪2 × n আকারের করিডোর 1 × 2 টাইলে (খাড়া বা শোয়ানো) কতভাবে ঢাকা যায়? সিঁড়ির উত্তরের সাথে সম্পর্ক কী? (মাঝারি)
বাঁ দিক থেকে দেখুন: প্রথম কলাম হয় একটি খাড়া টাইলে ঢাকা (বাকি 2 × (n − 1)), নয় দুটি শোয়ানো টাইল একসাথে প্রথম দুই কলাম ঢাকে (বাকি 2 × (n − 2)); একটি শোয়ানো টাইল একা থাকতে পারে না, কারণ তার নিচের ঘর তখন আরেকটি শোয়ানো টাইল ছাড়া ঢাকা যায় না। তাই tn = tn−1 + tn−2, t1 = 1, t2 = 2: সিঁড়ির একই সংখ্যা। Bijection: খাড়া টাইল ↔ ১ ধাপ, দুটি শোয়ানো ↔ ২ ধাপ। 2 × 10-এ ৮৯. রিভিশন: পর্ব ৪৪ Recurrence দিয়ে মডেল, পর্ব ৩৭ গণনার কৌশল — bijection.
প্র ২৫3 × n বোর্ড 1 × 2 domino-তে কতভাবে ঢাকা যায়? n বিজোড় হলে কী হয়? (কঠিন)
n বিজোড় হলে ০: ঘর 3n টি, বিজোড়, আর প্রতিটি domino ২টি ঘর ঢাকে। n জোড় হলে মানগুলো (n = 2, 4, 6, 8) 3, 11, 41, 153, আর তারা মেনে চলে an = 4an−2 − an−4; যেমন 4 · 41 − 11 = 153. এই recurrence আসে একটি সহায়ক গণনা থেকে: এক কোণার ঘর বাদ দেওয়া 3 × n বোর্ডের tiling, যার সাথে মূল গণনা পরস্পর জড়িয়ে থাকে; দুটি মিলিয়ে সহায়কটি বাদ দিলে এই রূপ। ইন্টারভিউতে পুরো derivation না পারলেও “বিজোড়ে অসম্ভব”, ছোট মানের brute force আর অনুমিত recurrence-এর যাচাই দেখানোই শক্ত উত্তর। রিভিশন: পর্ব ৪৫ Linear recurrence সমাধান.
প্র ২৬n জোড়া বন্ধনী দিয়ে কয়টি সঠিক (balanced) স্ট্রিং লেখা যায়? n = 3 আর n = 5-এ কত? (মাঝারি)
উত্তর Catalan সংখ্যা Cn = C(2n, n)/(n + 1): n = 3-এ ৫ — ((())), (()()), (())(), ()(()), ()()() — আর n = 5-এ ৪২. Recurrence: প্রথম “(” যে “)”-এর সাথে জোড়া বাঁধে, তার ভেতরে k জোড়া আর বাইরে n − 1 − k জোড়া, তাই Cn = Σ CkCn−1−k. একই সংখ্যা গোনে n টি node-এর binary tree-র আকার আর কর্ণ না-পেরোনো lattice path; ইন্টারভিউতে “এটি Catalan” চেনাই অর্ধেক কাজ। রিভিশন: পর্ব ৫০ বিশেষ সংখ্যা.
from functools import lru_cache
from itertools import product
from math import comb
def tilings(rows, cols): # count domino tilings by brute force
@lru_cache(maxsize=None)
def go(filled):
empty = [(r, c) for c in range(cols) for r in range(rows) if (r, c) not in filled]
if not empty: return 1
r, c = empty[0] # first empty cell, column by column
total = 0
for nb in ((r + 1, c), (r, c + 1)): # vertical or horizontal domino
if nb[0] < rows and nb[1] < cols and nb not in filled:
total += go(filled | {(r, c), nb})
return total
return go(frozenset())
ways = [1, 1] # stairs with steps of 1 or 2
for n in range(2, 11): ways.append(ways[-1] + ways[-2])
print("stairs n=1..10:", ways[1:])
print("2 x n tilings :", [tilings(2, n) for n in range(1, 11)])
print("3 x n, n even :", [tilings(3, n) for n in (2, 4, 6, 8)], "| 4*41-11 =", 4 * 41 - 11)
def balanced(s): # never below zero, ends at zero
depth = 0
for ch in s:
depth += 1 if ch == "(" else -1
if depth < 0: return False
return depth == 0
print("balanced strings:", [sum(balanced(p) for p in product("()", repeat=2 * n)) for n in range(1, 8)])
print("C(2n,n)/(n+1) :", [comb(2 * n, n) // (n + 1) for n in range(1, 8)])আসল আউটপুট (ব্রাউজারের Python):
stairs n=1..10: [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
2 x n tilings : [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
3 x n, n even : [3, 11, 41, 153] | 4*41-11 = 153
balanced strings: [1, 2, 5, 14, 42, 132, 429]
C(2n,n)/(n+1) : [1, 2, 5, 14, 42, 132, 429]
সিঁড়ির DP আর 2 × n বোর্ডের brute force tiling হুবহু একই তালিকা, শেষে ৮৯. 3 × n-এ 3, 11, 41, 153, আর 4 · 41 − 11 = 153 recurrence মেলায়। সব 22nটি বন্ধনী-স্ট্রিং পরীক্ষা করে পাওয়া সংখ্যা Catalan সূত্রের সমান। Brute force n = 7-এ ১৬,৩৮৪টি স্ট্রিং দেখে; n = 20-এ তা হতো প্রায় ১০১২, তখন শুধু সূত্র বা DP.
৭ · Invariant আর monovariant — যা বদলায় না, আর যা শুধু একদিকে চলে
রিভিশন: পর্ব ১৩ Program-এর সঠিকতা — loop invariant, পর্ব ১১ Strong induction আর well-ordering; পূর্ণ লিখিত প্রমাণ: পর্ব ৮২ প্রমাণ লেখার অনুশীলন
“কখনো কি পৌঁছানো যাবে?” ধরনের প্রশ্নে খুঁজুন একটি রাশি যা প্রতিটি চালে একই থাকে (invariant): শুরু আর লক্ষ্যে তার মান আলাদা হলে লক্ষ্য অসম্ভব। “প্রক্রিয়াটি কি থামবে?” প্রশ্নে খুঁজুন একটি রাশি যা প্রতিটি চালে কড়াভাবে বাড়ে বা কমে আর সীমাবদ্ধ (monovariant): তাহলে অসীমকাল চলা অসম্ভব।
ছবি ৮৩.২ — বিপরীত দুই কোণা কাটা ৪ × ৪ বোর্ড
কাটা দুটি ঘরই একই রঙের; প্রতিটি domino এক গাঢ় আর এক হালকা ঘর ঢাকে
প্র ২৭৮ × ৮ দাবার বোর্ডের বিপরীত দুই কোণার ঘর কেটে ফেলা হলো। বাকি ৬২টি ঘর কি ৩১টি 1 × 2 domino দিয়ে ঢাকা যায়? (সহজ)
না। দাবার রং দিন। বিপরীত কোণার দুটি ঘর একই রঙের (দুটির সারি আর কলামের যোগফলের parity এক), তাই বাকি থাকে এক রঙের ৩০টি আর অন্য রঙের ৩২টি। প্রতিটি domino পাশাপাশি দুটি ঘর ঢাকে, যারা সবসময় ভিন্ন রঙের; তাই যেকোনো আংশিক ঢাকায় “দুই রঙের ঢাকা ঘরের পার্থক্য” = 0, একটি invariant. পুরো ঢাকায় পার্থক্য হতো 32 − 30 = 2. বিরোধ। ∎ ছবি ৮৩.২ একই যুক্তি ৪ × ৪-এ দেখায়। শিক্ষা: গণনা (৬২ জোড়) ঠিক থাকলেও গঠন অসম্ভব হতে পারে। রিভিশন: পর্ব ৮২ প্রমাণ লেখার অনুশীলন.
প্র ২৮বোর্ডে ১ থেকে ১০ লেখা। প্রতিবার যেকোনো দুটি সংখ্যা a, b মুছে |a − b| লেখা হয়, যতক্ষণ না একটিই সংখ্যা থাকে। শেষ সংখ্যাটি কি ০ হতে পারে? (মাঝারি)
না, শেষ সংখ্যা সবসময় বিজোড়। Invariant: সব সংখ্যার যোগফলের parity. একটি চালে যোগফল থেকে a + b যায় আর |a − b| আসে; a + b আর |a − b|-এর পার্থক্য 2·min(a, b), জোড়, তাই parity একই থাকে। শুরুতে 1 + 2 + … + 10 = 55, বিজোড়। শেষে একটিই সংখ্যা, তাই সেটি বিজোড়, ০ নয়। ∎ নিচের ব্লক n ≤ 8-এ প্রতিটি সম্ভাব্য চালের ক্রম ঘুরে দেখায়, শেষ সংখ্যার parity সবসময় যোগফলের parity-র সমান।
প্র ২৯একটি m × n সারণিতে পূর্ণসংখ্যা লেখা। একটি চালে যেকোনো একটি সারি বা কলামের সব সংখ্যার চিহ্ন উল্টানো যায়। প্রমাণ করুন: এমন কিছু চাল আছে, যার পরে প্রতিটি সারি আর কলামের যোগফল ঋণাত্মক নয়। (কঠিন)
- নিয়ম: যতক্ষণ কোনো সারি বা কলামের যোগফল s < 0, সেটির চিহ্ন উল্টান। — প্রক্রিয়া
- সেই চালে পুরো সারণির মোট যোগফল −2s পরিমাণ বাড়ে, যা ধনাত্মক; মোট যোগফল কড়াভাবে বাড়ে। — monovariant
- প্রতিটি ঘরের মান শুধু ±(মূল মান) হতে পারে, তাই সম্ভাব্য সারণি বড়জোর 2mnটি, আর মোট যোগফল সসীম কয়েকটি মানের একটি। — সসীমতা
- কড়াভাবে বাড়তে থাকা রাশি সসীম মানের মধ্যে অসীমবার বাড়তে পারে না, তাই প্রক্রিয়া থামে; থামে কেবল যখন কোনো সারি বা কলামের যোগফল ঋণাত্মক নয়। ∎
প্রমাণটি অস্তিত্বের, আর algorithm-ও দেয়। একই ধারণায় দেখানো হয় যে অনেক local search থামে। রিভিশন: পর্ব ১১ Well-ordering, পর্ব ১৩ Program-এর সঠিকতা.
from functools import lru_cache
import random
# 1) invariant: write 1..n; replace any two numbers a, b by |a - b| until one is left
@lru_cache(maxsize=None)
def finals(nums): # nums: sorted tuple
if len(nums) == 1: return frozenset(nums)
out = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
rest = nums[:i] + nums[i + 1:j] + nums[j + 1:]
out |= finals(tuple(sorted(rest + (abs(nums[i] - nums[j]),))))
return frozenset(out)
for n in range(2, 9):
print(f"n={n}: sum parity {n * (n + 1) // 2 % 2}, possible last numbers {sorted(finals(tuple(range(1, n + 1))))}")
# 2) monovariant: flip the sign of a row or column whose sum is negative
rng = random.Random(9)
T = [[rng.randint(-9, 9) for _ in range(5)] for _ in range(4)]
totals = [sum(map(sum, T))]
while True:
bad = [("r", i) for i in range(4) if sum(T[i]) < 0] + [("c", j) for j in range(5) if sum(r[j] for r in T) < 0]
if not bad: break
kind, i = bad[0]
if kind == "r": T[i] = [-x for x in T[i]]
else:
for r in T: r[i] = -r[i]
totals.append(sum(map(sum, T)))
print("table total after each flip:", totals)
print("row sums:", [sum(r) for r in T], "| column sums:", [sum(r[j] for r in T) for j in range(5)])আসল আউটপুট (ব্রাউজারের Python):
n=2: sum parity 1, possible last numbers [1]
n=3: sum parity 0, possible last numbers [0, 2]
n=4: sum parity 0, possible last numbers [0, 2, 4]
n=5: sum parity 1, possible last numbers [1, 3, 5]
n=6: sum parity 1, possible last numbers [1, 3, 5]
n=7: sum parity 0, possible last numbers [0, 2, 4, 6]
n=8: sum parity 0, possible last numbers [0, 2, 4, 6, 8]
table total after each flip: [-7, -1, 5, 7, 27, 41, 43, 61, 67]
row sums: [17, 13, 18, 19] | column sums: [20, 13, 20, 11, 3]
প্রতিটি n-এ শেষ সংখ্যার সম্ভাব্য মান সব একই parity-র, আর তা 1 + … + n-এর parity: যোগফল বিজোড় হলে ০ কখনো আসে না। Seed দেওয়া ৪ × ৫ সারণিতে আটটি চালে মোট যোগফল −৭ থেকে ৬৭ পর্যন্ত প্রতিবার বেড়েছে, আর শেষে সব সারি ও কলামের যোগফল অঋণাত্মক।
৮ · Combinatorial game-এর শুরু — Nim আর XOR-এর প্রমাণ
রিভিশন: পর্ব ১১ Strong induction আর well-ordering (কাঠি তোলার খেলা), পর্ব ১২ Recursive সংজ্ঞা
দুজন পালা করে চাল দেন, দুজনের চাল একই রকম, ভাগ্যের কোনো ভূমিকা নেই, আর যিনি শেষ চাল দেন তিনি জেতেন। এমন খেলায় প্রতিটি অবস্থান হয় হারা অবস্থান (যাঁর পালা, তিনি নিখুঁত প্রতিপক্ষের বিরুদ্ধে হারবেন), নয় জেতা অবস্থান। সংজ্ঞাটি recursive: কোনো চাল নেই এমন অবস্থান হারা; যেখান থেকে অন্তত একটি চাল হারা অবস্থানে যায়, তা জেতা; যেখান থেকে সব চাল জেতা অবস্থানে যায়, তা হারা।
প্র ৩০একটি স্তূপে ২০টি কাঠি। দুজন পালা করে ১, ২ বা ৩টি কাঠি তোলেন; শেষ কাঠি যিনি তোলেন তিনি জেতেন। প্রথম জন কি জিততে পারেন? ২২টি হলে? (সহজ)
হারা অবস্থান ঠিক ৪-এর গুণিতকগুলো; পর্ব ১১-এ strong induction-এ এটি প্রমাণিত। ২০ ৪-এর গুণিতক, তাই প্রথম জন হারবেন, যদি দ্বিতীয় জন প্রতিবার মোট ৪ পূরণ করেন: প্রথম জন k টি তুললে তিনি তোলেন 4 − k টি। ২২-এ প্রথম জন ২টি তুলে ২০-এ পাঠান, তারপর একই “৪ পূরণ” কৌশলে জেতেন। এটি Nim-এর এক স্তূপের একটি সীমিত রূপ; পরের প্রশ্নে পুরো Nim. রিভিশন: পর্ব ১১ Strong induction.
প্র ৩১Nim: কয়েকটি স্তূপ; প্রতিটি চালে একটি স্তূপ থেকে অন্তত একটি (যত খুশি) কাঠি তোলা যায়; শেষ কাঠি যিনি তোলেন তিনি জেতেন। প্রমাণ করুন: অবস্থানটি হারা ⇔ স্তূপের আকারগুলোর XOR (nim-sum) 0. (কঠিন)
স্তূপ h1, …, hk, আর nim-sum s = h1 ⊕ … ⊕ hk. দুটি lemma, তারপর strong induction.
- Lemma ১: s = 0 হলে প্রতিটি চালের পরে s ≠ 0. একটি স্তূপ hi থেকে h' < hi-তে গেলে নতুন nim-sum s' = s ⊕ hi ⊕ h' = hi ⊕ h' ≠ 0, কারণ h' ≠ hi. — XOR-এর ধর্ম: a ⊕ a = 0
- Lemma ২: s ≠ 0 হলে এমন একটি চাল আছে, যার পরে s = 0. s-এর সবচেয়ে বাঁয়ের 1-বিটটি d নিন; কোনো স্তূপ hi-তে বিট d আছে (নইলে s-এ থাকত না)। h' = hi ⊕ s নিন: বিট d-তে 1 থেকে 0 হয়, তার ওপরের বিট একই, তাই h' < hi, অর্থাৎ বৈধ চাল; নতুন nim-sum s ⊕ hi ⊕ h' = s ⊕ s = 0. — binary তুলনা
- Induction (মোট কাঠির সংখ্যা N-এর ওপর): N = 0-তে s = 0 আর কোনো চাল নেই, তাই হারা। ছোট সব N-এ দাবি সত্য ধরে নিন। s = 0 হলে Lemma ১ অনুযায়ী প্রতিটি চাল কম কাঠির একটি s ≠ 0 অবস্থানে যায়, যা hypothesis অনুযায়ী জেতা; তাই বর্তমানটি হারা। s ≠ 0 হলে Lemma ২-এর চাল একটি হারা অবস্থানে পাঠায়; তাই বর্তমানটি জেতা। ∎
পর্ব ১১-এ যেমন বলা আছে, Nim-এর এই সমাধান সাধারণত Charles L. Bouton-এর (১৯০১) বলে উল্লেখ করা হয়। নিচের stepper (3, 4, 5) থেকে একটি পুরো খেলা দেখায়।
| অবস্থান | |
| binary | |
| nim-sum | |
| এরপর চাল |
- শুরু। 3 ⊕ 4 ⊕ 5 = 2 ≠ 0, তাই ক জিততে পারেন। s = 010-এর বাঁয়ের 1-বিট আছে ৩-এর স্তূপে (011); 3 ⊕ 2 = 1, তাই সেখান থেকে ২টি তুলে ১ রাখুন।
- ক-এর চালের পরে। (1, 4, 5)-এ nim-sum 0. Lemma ১ অনুযায়ী খ যা-ই করুন, nim-sum ০ থাকবে না। খ ৫-এর স্তূপ থেকে ৩টি তুললেন।
- খ-এর চালের পরে। (1, 4, 2)-এ nim-sum 111 = 7. বাঁয়ের বিট (4-এর ঘর) আছে ৪-এর স্তূপে; 4 ⊕ 7 = 3, তাই ক ১টি তুলে ৩ রাখেন।
- ক-এর চালের পরে। (1, 3, 2)-এ আবার 0. খ ৩-এর পুরো স্তূপ তুলে নিলেন।
- খ-এর চালের পরে। (1, 0, 2)-এ nim-sum 3. বাঁয়ের বিট (2-এর ঘর) আছে ২-এর স্তূপে; 2 ⊕ 3 = 1, তাই ক ১টি তোলেন।
- ক-এর চালের পরে। (1, 0, 1)-এ 0: দুটি সমান স্তূপ, ক এখন খ-এর চাল নকল করলেই জেতেন। খ একটি স্তূপের ১টি তুললেন।
- খ-এর চালের পরে। (0, 0, 1)-এ nim-sum 1; ক শেষ কাঠিটি তোলেন।
- শেষ। (0, 0, 0): খ-এর কোনো চাল নেই, আর শেষ কাঠি ক তুলেছেন। প্রতিবার ক nim-sum ০ বানিয়েছেন, ঠিক Lemma ২-এর মতো।
from functools import lru_cache
from itertools import product
# normal play: the player who takes the last stick wins
@lru_cache(maxsize=None)
def wins(heaps): # True if the player to move can force a win
for i, h in enumerate(heaps):
for take in range(1, h + 1):
nxt = tuple(sorted(heaps[:i] + (h - take,) + heaps[i + 1:]))
if not wins(nxt):
return True
return False # no move, or every move hands a win over
agree = total = 0
for a, b, c in product(range(8), repeat=3):
total += 1
agree += (not wins(tuple(sorted((a, b, c))))) == (a ^ b ^ c == 0)
print(f"3 heaps up to 7: {agree} of {total} positions agree with 'lose iff XOR = 0'")
def good_moves(heaps):
x = heaps[0] ^ heaps[1] ^ heaps[2]
return [(i, h, h ^ x) for i, h in enumerate(heaps) if h ^ x < h]
print("(3,4,5): nim-sum", 3 ^ 4 ^ 5, "| moves to nim-sum 0 (heap, from, to):", good_moves((3, 4, 5)))
# one heap, take 1, 2 or 3 sticks
L = []
for n in range(25):
if not any(n - m in L for m in (1, 2, 3) if n - m >= 0): L.append(n)
print("take-1-2-3 losing positions:", L)আসল আউটপুট (ব্রাউজারের Python):
3 heaps up to 7: 512 of 512 positions agree with 'lose iff XOR = 0'
(3,4,5): nim-sum 2 | moves to nim-sum 0 (heap, from, to): [(0, 3, 1)]
take-1-2-3 losing positions: [0, 4, 8, 12, 16, 20, 24]
৭ পর্যন্ত তিনটি স্তূপের সব ৫১২টি অবস্থানে recursive সংজ্ঞায় পাওয়া জয়-পরাজয় XOR নিয়মের সাথে মেলে। (3, 4, 5)-এ nim-sum 2, আর একমাত্র ভালো চাল প্রথম স্তূপ ৩ থেকে ১-এ: stepper-এর প্রথম চাল। এক স্তূপের ১-২-৩ খেলায় হারা অবস্থান 0, 4, 8, …, 24.
৯ · Contest-ধাঁচের সমস্যা — আগে সংকেত, পরে সমাধান
রিভিশন: পর্ব ২২ Modular arithmetic, পর্ব ৩২ Pigeonhole principle, পর্ব ৪৯ Inclusion–exclusion আর derangement; আরও সমস্যা: DSA কোর্সের পর্ব ৭২
প্রতিটি উত্তরের প্রথম লাইনে শুধু একটি সংকেত। সংকেত পড়ে আবার চেষ্টা করুন, তারপর বাকিটা। Contest-এ সীমা দেখে আগেই ঠিক করুন কোন জটিলতা লাগবে: n = 105 হলে O(n²) সাধারণত সময়ে কুলায় না।
প্র ৩২n টি পূর্ণসংখ্যা আর একটি k দেওয়া। কয়টি জোড়া i < j-তে ai + aj k দিয়ে বিভাজ্য? n = 105 পর্যন্ত। (মাঝারি)
সংকেত: শুধু ভাগশেষ গুরুত্বপূর্ণ।
সমাধান: cr = যতগুলোর ভাগশেষ r. যোগফল k দিয়ে বিভাজ্য ⇔ ভাগশেষ দুটি r আর k − r (mod k). তাই উত্তর C(c0, 2) + Σ1 ≤ r < k/2 cr·ck−r, আর k জোড় হলে আরও C(ck/2, 2). r = 0 আর r = k/2-এ দুটি সংখ্যাই একই দলের, তাই সেখানে গুণ নয়, C(c, 2); এটি ভুলে গেলে দুবার গোনা হয়। সময় O(n + k). রিভিশন: পর্ব ২২ Modular arithmetic, পর্ব ৩৩ Combination.
প্র ৩৩প্রমাণ করুন: যেকোনো n টি পূর্ণসংখ্যার তালিকায় পাশাপাশি কিছু সংখ্যার (অন্তত একটি) একটি অংশ আছে, যার যোগফল n দিয়ে বিভাজ্য। সেটি O(n)-এ খুঁজুন। (মাঝারি)
সংকেত: prefix sum আর pigeonhole.
- S0 = 0, Sj = a1 + … + aj (j = 1…n): মোট n + 1টি prefix sum.
- Mod n-এ ভাগশেষ মাত্র n টি, তাই pigeonhole অনুযায়ী দুটি Si, Sj (i < j)-এর ভাগশেষ সমান।
- তাহলে Sj − Si = ai+1 + … + aj ≡ 0 (mod n), আর অংশটি খালি নয়, কারণ i < j. ∎
Algorithm: একটি dictionary-তে প্রতিটি ভাগশেষ প্রথম কোথায় দেখা গেছে, তা রাখুন; একই ভাগশেষ আবার এলেই উত্তর। ঋণাত্মক সংখ্যাতেও কাজ করে, যদি ভাগশেষ 0…n − 1-এ নেওয়া হয়। রিভিশন: পর্ব ৩২ Pigeonhole principle.
প্র ৩৪১ থেকে ১০০০-এর মধ্যে কয়টি সংখ্যা ২, ৩ বা ৫-এর অন্তত একটি দিয়ে বিভাজ্য? (সহজ)
সংকেত: একক সেটগুলো যোগ, জোড়ার ছেদ বিয়োগ, তিনটির ছেদ আবার যোগ।
সমাধান: Inclusion–exclusion: ⌊1000/2⌋ + ⌊1000/3⌋ + ⌊1000/5⌋ − ⌊1000/6⌋ − ⌊1000/10⌋ − ⌊1000/15⌋ + ⌊1000/30⌋ = 500 + 333 + 200 − 166 − 100 − 66 + 33 = 734. ২ আর ৩ দুটি দিয়েই বিভাজ্য মানে ৬ দিয়ে বিভাজ্য, কারণ তারা পরস্পর মৌলিক; ৪ আর ৬ হলে ছেদ হতো ১২-র গুণিতক, ২৪-এর নয়। রিভিশন: পর্ব ৪৯ Inclusion–exclusion.
প্র ৩৫৫ জন অতিথির ছাতা এলোমেলোভাবে ফেরত দেওয়া হলো। কেউই নিজের ছাতা না পাওয়ার কতগুলো উপায়, আর তার সম্ভাবনা কত? n বড় হলে সম্ভাবনা কোথায় যায়? (মাঝারি)
সংকেত: প্রথম অতিথি যাঁর ছাতা পেলেন, তিনি প্রথম জনের ছাতা পেলেন কি না।
সমাধান: এগুলো derangement. অতিথি ১ পান অতিথি j-এর ছাতা (n − 1টি উপায়)। j পান ১-এর ছাতা হলে বাকি n − 2 জন নিজেদের মধ্যে derangement: Dn−2; না পেলে j-এর জন্য “১-এর ছাতা নিষেধ” ঠিক “নিজের ছাতা নিষেধ”-এর মতো, তাই Dn−1. সুতরাং Dn = (n − 1)(Dn−1 + Dn−2), D1 = 0, D2 = 1: 0, 1, 2, 9, 44. সম্ভাবনা 44/120 ≈ 0.367; n বাড়লে Dn/n! → 1/e ≈ 0.368 (inclusion–exclusion-এর সূত্র থেকে)। রিভিশন: পর্ব ৪৯ Inclusion–exclusion আর derangement.
import random
from itertools import permutations
from collections import Counter
rng = random.Random(83)
a = [rng.randrange(1, 100) for _ in range(300)]
k = 7 # an odd k keeps the formula short
# 1) pairs i < j with (a[i] + a[j]) divisible by k: brute force vs remainder counting
brute = sum((a[i] + a[j]) % k == 0 for i in range(len(a)) for j in range(i + 1, len(a)))
cnt = Counter(x % k for x in a)
fast = cnt[0] * (cnt[0] - 1) // 2 + sum(cnt[r] * cnt[k - r] for r in range(1, (k + 1) // 2))
print("pairs:", brute, fast)
# 2) any n integers: some block a[i..j] has sum divisible by n (pigeonhole on prefix sums)
def block(xs):
n, seen, s = len(xs), {0: -1}, 0
for j, x in enumerate(xs):
s = (s + x) % n
if s in seen: return seen[s] + 1, j
seen[s] = j
tests = [[rng.randrange(-50, 50) for _ in range(rng.randrange(1, 12))] for _ in range(2000)]
print("block found in every test:", all(sum(t[i:j + 1]) % len(t) == 0 for t in tests for i, j in [block(t)]))
# 3) 1..1000 divisible by 2, 3 or 5: brute force vs inclusion-exclusion
N = 1000
print("2,3,5:", sum(any(x % p == 0 for p in (2, 3, 5)) for x in range(1, N + 1)),
N//2 + N//3 + N//5 - N//6 - N//10 - N//15 + N//30)
# 4) derangements: brute force vs D(n) = (n-1)(D(n-1) + D(n-2))
D = [1, 0]
for n in range(2, 8): D.append((n - 1) * (D[-1] + D[-2]))
print("D(n):", [sum(all(p[i] != i for i in range(n)) for p in permutations(range(n))) for n in range(1, 8)], D[1:])আসল আউটপুট (ব্রাউজারের Python):
pairs: 6438 6438
block found in every test: True
2,3,5: 734 734
D(n): [0, 1, 2, 9, 44, 265, 1854] [0, 1, 2, 9, 44, 265, 1854]
৩০০টি seed-দেওয়া সংখ্যায় brute force আর ভাগশেষ-গণনা দুটিই ৬৪৩৮টি জোড়া পায়; brute force প্রায় ৪৫ হাজার জোড়া দেখে, ভাগশেষ-পদ্ধতি ৩০০টি সংখ্যা একবার। ২০০০টি এলোমেলো তালিকার প্রতিটিতে prefix-sum পদ্ধতি একটি বিভাজ্য অংশ পায়। Inclusion–exclusion-এর ৭৩৪ আর derangement-এর তালিকা দুটিই brute force-এর সমান।
১০ · শেষ সপ্তাহের প্রস্তুতি, অভিনন্দন আর পরের পথ
পুরো কোর্সের মানচিত্র: কোর্স হোম
ইন্টারভিউ বা contest-এর আগের সাত দিন
| দিন | কাজ | কোথায় |
|---|---|---|
| ১ | যুক্তির ধাঁধা জোরে বলা; পাঁচ ধাপের চক্র অভ্যাস | অংশ ১–২, পর্ব ০২, ০৭, ৩২ |
| ২ | গণনা আর সম্ভাবনা: প্রত্যাশার সমীকরণ হাতে লেখা | অংশ ৩, পর্ব ৩৩, ৩৮–৪২ |
| ৩ | Bit trick, modular inverse, ঘাত; একটি ছোট কোড নিজে লেখা | অংশ ৪, পর্ব ২০–২৪ |
| ৪ | Graph আর tree: প্রতিটি উপপাদ্যের শর্ত মুখে বলা | অংশ ৫, পর্ব ৫৬–৬৭ |
| ৫ | Recurrence আর DP: শেষ ধাপ ধরে recurrence, তারপর টেবিল | অংশ ৬, পর্ব ৪৪–৫০ |
| ৬ | Invariant আর Nim; একটি প্রমাণ খাতায় পুরো লেখা | অংশ ৭–৮, পর্ব ৮২ |
| ৭ | ঘড়ি ধরে চারটি contest-সমস্যা; নতুন বিষয় নয়, বিশ্রাম | অংশ ৯ |
আপনি ডিসক্রিট ম্যাথমেটিক্স কোর্সের ৮৩টি পর্ব পেরিয়ে এসেছেন: যুক্তি আর প্রমাণ, সেট আর ফাংশন, সংখ্যাতত্ত্ব আর cryptography-র গণিত, গণনা আর সম্ভাবনা, recurrence আর generating function, relation, graph আর tree, বীজগাণিতিক কাঠামো, গণনার মডেল আর software যাচাই। সবচেয়ে বড় অর্জন কোনো সূত্র নয়, একটি অভ্যাস: যেকোনো দাবি দেখে জিজ্ঞেস করা, “শর্ত কী? ছোট ক্ষেত্রে মেলে কি? প্রমাণ কোথায়? আর কোডে যাচাই করেছি কি?”
এরপর কী করবেন
- আবার ঝালিয়ে নিন: যে প্রশ্নে আটকেছেন, কোর্স হোম থেকে সেই মডিউলে ফিরে যান। মডিউল ১৪-এর বাকি পাতা: পর্ব ৭৮-এর ক্যাপস্টোন, পর্ব ৭৯-এর numerical, পর্ব ৮০-এর MCQ, পর্ব ৮১-এর লিখিত ও মৌখিক প্রশ্ন, আর পর্ব ৮২ প্রমাণ লেখার অনুশীলন.
- কোডে প্রয়োগ: DSA কোর্সের পর্ব ৭১ · কোডিং ইন্টারভিউর সমস্যা আর পর্ব ৭২ · কনটেস্টের অনুশীলন; এখানকার গণিত সেখানে algorithm হয়ে আসে।
- পাশের সম্পূর্ণ কোর্স: AI-এর ভিত্তি (logic আর সম্ভাবনা), মেশিন লার্নিং, Cybersecurity (বাস্তব cryptography) আর Computer Architecture (Boolean algebra হার্ডওয়্যারে)।
- পরের গভীরতা: automaton, grammar আর computability নিয়ে একটি আলাদা Theory of Computation কোর্স প্রস্তুত হচ্ছে।
এই পাতা কোনো চাকরি বা contest-এর ফলের নিশ্চয়তা দেয় না। আপনার হাতে যা আছে, তা হলো প্রমাণ করা আর যাচাই করার অভ্যাস, আর না-জানা জায়গায় সৎভাবে “এখানে আমি নিশ্চিত নই” বলতে পারা।
AI-এর যুগে — ধাঁধা আর গণনায় AI-কে যাচাই
AI সহকারী ধাঁধার উত্তর আত্মবিশ্বাসী ভাষায় দেয়, আর প্রায়ই ভুল দেয়। পরিচিত ভুল: জনপ্রিয় ধাঁধার মুখস্থ উত্তর বলা, অথচ প্রশ্নের নিয়ম একটু আলাদা (জলদস্যুতে “অর্ধেক” বনাম “অর্ধেকের বেশি”); গণনায় off-by-one, দুবার গোনা বা ক্রম গুলিয়ে ফেলা; শর্ত ছাড়া উপপাদ্য (ঋণাত্মক edge-এ Dijkstra, মৌলিক নয় এমন modulus-এ Fermat); চক্রাকার বা অপূর্ণ প্রমাণ (Nim-এ শুধু একটি lemma); বড় সংখ্যার modular হিসাবে ভুল; আর না-থাকা বই বা প্রবন্ধের নাম। তাই AI-কে উত্তরদাতা নয়, ইন্টারভিউয়ার আর পরীক্ষক বানান।
নকল ইন্টারভিউর একটি কাঠামো; কোনো আসল প্রতিষ্ঠানের প্রশ্ন বা ব্যক্তিগত তথ্য দেবেন না:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
আরও গভীরে: পর্ব ১১-এ যেমন বলা আছে, ১৯৩০-এর দশকে Roland Sprague আর Patrick Grundy স্বাধীনভাবে দেখান, প্রতিটি impartial খেলার প্রতিটি অবস্থান কোনো এক আকারের Nim স্তূপের সমতুল্য; সেই আকার অবস্থানটির Grundy সংখ্যা, আর কয়েকটি খেলা একসাথে খেললে Grundy সংখ্যাগুলোর XOR নিলেই চলে। ১-২-৩ কাঠির খেলায় n কাঠির Grundy সংখ্যা n mod 4. Coupon collector-এর গড়ের চারপাশে মান কতটা ছড়ায়, তা variance আর Chebyshev দিয়ে মাপা যায়, যা পর্ব ৪১ Variance আর concentration-এ আছে।
পরীক্ষা ও ইন্টারভিউতে: “১০০০ বোতলের একটিতে বিষ, ১০ জন পরীক্ষক, সবাই একবারেই চাখবেন” — প্রতিটি বোতলের নম্বর ১০ বিটে লিখুন, বিট i ১ হলে পরীক্ষক i চাখেন; কারা অসুস্থ হলেন তা binary-তে বোতলের নম্বর, কারণ 210 = 1024 ≥ 1000. “n vertex-এর tree-তে কয়টি edge?” — n − 1. “Graph-এ বিজোড় degree-র vertex কয়টি থাকতে পারে?” — সবসময় জোড় সংখ্যক। “পরপর তিনটি পূর্ণসংখ্যার গুণফল কি সবসময় ৬ দিয়ে বিভাজ্য?” — হ্যাঁ, একটি ২-এর আর একটি ৩-এর গুণিতক থাকে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমইন্টারভিউতে একটি অচেনা গণিতের প্রশ্ন সমাধানের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রশ্ন নিজের ভাষায় বলা আর শর্ত জিজ্ঞেস করা
- সবচেয়ে ছোট কয়েকটি ক্ষেত্র হাতে সমাধান করা
- প্যাটার্ন দেখে একটি অনুমান জোরে বলা
- অনুমানটি প্রমাণ করা (pigeonhole, induction, invariant)
- সীমার মান আর brute force দিয়ে উত্তর যাচাই করা
(সহজ) x = 48 হলে x & (x - 1)-এর মান কত?
- 32
- 47
- 16
- 0
(মাঝারি) Nim-এর অবস্থান (2, 5, 6), শেষ কাঠি যিনি তোলেন তিনি জেতেন। যাঁর পালা, তাঁর জন্য কোনটি ঠিক?
- তিনি হারবেন, কারণ nim-sum ০
- ৬-এর পুরো স্তূপ তুলে জিতবেন
- ৫-এর স্তূপ থেকে ১টি তুলে (2, 4, 6) বানালে জিতবেন
- ২-এর স্তূপ থেকে ১টি তুলে জিতবেন
(মাঝারি) ৪ রকম কুপন, প্রতিটি সমান সম্ভাবনায় আসে। সব ৪ রকম পেতে গড়ে কয়টি কুপন লাগবে?
- ৪
- 25/3 ≈ ৮.৩৩
- ১৬
- ১০
(মাঝারি) একটি connected undirected simple graph-এ ১০টি vertex আর ১০টি edge. নিশ্চিতভাবে কোনটি সত্য?
- এটি একটি tree
- এটি bipartite
- অন্তত দুটি আলাদা cycle আছে
- ঠিক একটি cycle আছে
(সহজ) একটি 3 × 7 বোর্ড 1 × 2 domino দিয়ে কতভাবে পুরো ঢাকা যায়?
- ০
- ২১
- ৪১
- ১৫৩
(কঠিন) বোর্ডে ১ থেকে ১২ লেখা। প্রতিবার দুটি সংখ্যা a, b মুছে |a − b| লেখা হয়, শেষে একটি সংখ্যা থাকে। নিশ্চিতভাবে কী বলা যায়?
- শেষ সংখ্যা সবসময় বিজোড়
- শেষ সংখ্যা সবসময় জোড়
- শেষ সংখ্যা সবসময় ০
- কিছুই নিশ্চিতভাবে বলা যায় না
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:
মনে রাখুন
- জোরে ভাবুন: শর্ত জিজ্ঞেস, ছোট ক্ষেত্র, প্যাটার্ন, প্রমাণ, যাচাই; অনুমান আর প্রমাণ আলাদা করে বলুন।
- ধাঁধার অস্ত্র: decision tree-র নিচের সীমা, pigeonhole, backward induction, আর truth table বা SAT.
- সম্ভাবনা: linearity of expectation আর অবস্থার সমীকরণ; প্রতিটি অনুমান (সমান সম্ভাবনা, স্বাধীনতা) মুখে বলুন।
- কোডিং ইন্টারভিউ: x & (x − 1), XOR-এর group ধর্ম, Fermat-এর inverse শুধু মৌলিক p-তে, Dijkstra শুধু অঋণাত্মক weight-এ।
- গণনা আর খেলা: শেষ ধাপ ধরে recurrence, অসম্ভবতায় invariant, থামায় monovariant, Nim-এ nim-sum ০ মানে হার।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল; দুর্বল মনে হওয়া মডিউলে ফিরে যান।
- পর্ব ৮২ · প্রমাণ লেখার অনুশীলন — ধাপে ধাপে আদর্শ উত্তররিভিশনInvariant, pigeonhole আর induction-এর পূর্ণ লিখিত প্রমাণ, নম্বর-ভাগসহ।
- DSA · পর্ব ৭১ · কোডিং ইন্টারভিউর সমস্যাপরের ধাপপ্যাটার্ন ধরে কোডিং সমস্যার সমাধান; এখানকার গণিত কোডে।
- DSA · পর্ব ৭২ · কনটেস্টের অনুশীলনপরের ধাপICPC-ধাঁচের সমস্যা সেট, সংখ্যাতত্ত্ব, গণনা আর graph.
- AI-এর ভিত্তি · পর্ব ৭৯ · ইন্টারভিউপাশের কোর্সLogic, search আর সম্ভাবনার ইন্টারভিউ প্রশ্ন, AI-এর দিক থেকে।
- Cybersecurity · কোর্স হোমপাশের কোর্সএই কোর্সের সংখ্যাতত্ত্বের বাস্তব cryptography-তে নিরাপদ ব্যবহার।