এই পর্বে যা শিখবেন
- Minimization আর maximization-এ approximation ratio-র সঠিক সংজ্ঞা, আর OPT না জেনেই ratio প্রমাণের নিম্নসীমা-কৌশল
- Maximal matching দিয়ে vertex cover-এর ২-approximation, তার প্রমাণ, আর কেন “বেশি degree আগে” greedy ধ্রুবক নিশ্চয়তা দেয় না
- Greedy set cover হাতে trace করা আর pricing যুক্তিতে H(n) নিশ্চয়তার প্রমাণ
- Metric TSP-এ MST দ্বিগুণ করার ২-approximation, Christofides-এর ১.৫, আর সাধারণ TSP-এ কেন কোনো ধ্রুবক ratio অসম্ভব
- List scheduling, knapsack-এর FPTAS, PTAS বনাম FPTAS আর inapproximability-র মূল ফলগুলো চেনা
১ · নিখুঁত না পেলে প্রমাণিত “যথেষ্ট ভালো” — approximation ratio
পর্ব ৬৪-এ দেখেছেন, vertex cover, set cover আর traveling salesman-এর মতো সমস্যা NP-hard: P ≠ NP হলে এদের নিখুঁত উত্তর বড় ইনপুটে দ্রুত বের করার কোনো উপায় নেই। পর্ব ৬৫-এ দেখেছেন বাস্তবে মানুষ কী করে — ছোট ইনপুটে brute force, বিশেষ গঠন কাজে লাগানো, আর heuristic. Heuristic প্রায়ই ভালো উত্তর দেয়, কিন্তু কতটা ভালো, তার কোনো নিশ্চয়তা দেয় না। এই পর্বের হাতিয়ার মাঝামাঝি: দ্রুত, আর সাথে একটি প্রমাণিত প্রতিশ্রুতি।
একটি approximation algorithm দুটি শর্ত মানে: (১) এটি polynomial time-এ চলে, আর (২) প্রতিটি ইনপুটে এর উত্তর সর্বোত্তম উত্তরের একটি নির্দিষ্ট গুণের মধ্যে থাকে। সর্বোত্তম উত্তরের মানকে লেখা হয় OPT, অ্যালগরিদমের উত্তরের মানকে ALG.
Approximation ratio ρ ≥ 1. Minimization সমস্যায় (খরচ কমানো) অ্যালগরিদমটি ρ-approximation, যদি প্রতিটি ইনপুট I-তে ALG(I) ≤ ρ · OPT(I). Maximization সমস্যায় (লাভ বাড়ানো) শর্তটি ALG(I) ≥ OPT(I) / ρ. ρ = 1 মানে নিখুঁত। কিছু বই maximization-এ ρ ≤ 1 লিখে ALG ≥ ρ · OPT বলে — একই কথা, শুধু রীতি আলাদা। ρ একটি ধ্রুবক হতে পারে (২), ইনপুটের আকারের ফাংশন হতে পারে (ln n), আবার 1 + ε-ও হতে পারে, যেখানে ε আপনি নিজে বেছে নেন।
উপমা — ঈদের আগে বাসের টিকিট। সবচেয়ে সস্তা টিকিট খুঁজতে আপনি সারা রাত সব কাউন্টার আর সব অ্যাপ ঘাঁটতে পারেন — সেটি নিখুঁত সমাধান, কিন্তু সময়ে কুলায় না। আপনার এক বন্ধু বলল, “আমার একটা নিয়ম আছে, দুই মিনিটে টিকিট কেটে দিই, আর কখনো সবচেয়ে সস্তা দামের দ্বিগুণের বেশি দিতে হয়নি — আর আমি এটা প্রমাণ করতে পারি।” প্রমাণের অংশটিই approximation অ্যালগরিদমকে সাধারণ “অভিজ্ঞতার নিয়ম” থেকে আলাদা করে।
| পদ্ধতি | সময় | উত্তরের মান | উদাহরণ |
|---|---|---|---|
| নিখুঁত (exact) | সবচেয়ে খারাপ ক্ষেত্রে exponential | OPT, নিশ্চিত | Brute force, branch and bound (পর্ব ৫৭), Held–Karp DP |
| Approximation | Polynomial | প্রতিটি ইনপুটে ≤ ρ · OPT, প্রমাণিত | এই পর্বের সব অ্যালগরিদম |
| Heuristic | সাধারণত দ্রুত | প্রায়ই ভালো, নিশ্চয়তা নেই | Nearest neighbour, local search |
২ · OPT না জেনে কীভাবে প্রমাণ — নিম্নসীমার কৌশল
এখানে একটি আপাত-ধাঁধা আছে। “ALG ≤ 2 · OPT” প্রমাণ করতে OPT-এর সাথে তুলনা করতে হবে — অথচ OPT বের করাই তো NP-hard! সমাধান: OPT-এর সাথে সরাসরি তুলনা না করে একটি মধ্যবর্তী সংখ্যা LB ব্যবহার করুন, যার দুটি গুণ আছে — (ক) LB দ্রুত হিসাব করা যায়, আর (খ) যেকোনো সমাধান, তাই সর্বোত্তমটিও, অন্তত LB খরচ করে: LB ≤ OPT. তারপর অ্যালগরিদমের খরচকে LB দিয়ে বাঁধুন:
LB হলো একটি lower bound (নিম্নসীমা)। পুরো খেলাটা ভালো নিম্নসীমা খোঁজার খেলা — যত শক্ত (OPT-এর কাছাকাছি) নিম্নসীমা, তত ছোট ρ প্রমাণ করা যায়। Maximization-এ একই কাজ উল্টো দিকে: একটি upper bound UB ≥ OPT খুঁজে দেখান ALG ≥ UB / ρ. এই পর্বের প্রতিটি অ্যালগরিদমের পেছনে একটি করে নিম্নসীমা:
| সমস্যা | নিম্নসীমা (≤ OPT) | কেন ≤ OPT | ফল |
|---|---|---|---|
| Vertex cover | একটি matching-এর edge-সংখ্যা | প্রতিটি matched edge-এর জন্য cover-এ আলাদা একটি vertex লাগে | ρ = 2 |
| Set cover | Pricing — উপাদানগুলোর “দাম” | সর্বোত্তম প্রতিটি সেট সর্বোচ্চ H(n) দাম বহন করে | ρ = H(n) |
| Metric TSP | MST-এর ওজন | ট্যুর থেকে একটি edge মুছলে spanning tree | ρ = 2 |
| Load balancing | max(সবচেয়ে বড় কাজ, মোট কাজ / m) | কোনো একটি মেশিনকে এতটা করতেই হবে | ρ = 2 − 1/m |
উপমা — ঢাকা থেকে চট্টগ্রামে মাল পাঠানো। সবচেয়ে সস্তা পরিবহন-পরিকল্পনা কত, আপনি জানেন না। কিন্তু জানেন, পথ যেটাই হোক, ট্রাকের জ্বালানিতেই অন্তত ৮,০০০ টাকা লাগবে — এটি নিম্নসীমা। আপনার পরিকল্পনায় খরচ ১৬,০০০ টাকা হলে, সর্বোত্তমটি না জেনেও নিশ্চিত বলতে পারেন: “আমার খরচ সর্বোত্তমের দ্বিগুণের বেশি নয়।”
৩ · Vertex cover — matching দিয়ে ২-approximation
একটি ছোট শহরে সাতটি মোড় (A থেকে G) আর নয়টি রাস্তা। রাতে প্রতিটি রাস্তায় নজর রাখতে হবে। একজন নিরাপত্তারক্ষী একটি মোড়ে দাঁড়ালে সেই মোড় থেকে বের হওয়া সব রাস্তা দেখতে পান। সবচেয়ে কম কতজন রক্ষী দিয়ে সব রাস্তা পাহারা দেওয়া যায়? গ্রাফের ভাষায় মোড় = vertex, রাস্তা = edge, আর আমরা খুঁজছি সবচেয়ে ছোট vertex cover — এমন vertex-এর সেট, যা প্রতিটি edge-এর অন্তত এক প্রান্ত ছোঁয়। এটি Richard Karp-এর ১৯৭২ সালের ২১টি NP-complete সমস্যার একটি।
অ্যালগরিদমটি আশ্চর্য রকম সরল: রাস্তাগুলো যেকোনো ক্রমে একে একে দেখুন। যে রাস্তার দুই প্রান্তেই এখনো কোনো রক্ষী নেই, তার দুই প্রান্তেই একজন করে রক্ষী বসান। যে রাস্তার এক প্রান্তে রক্ষী আছে, সেটি এড়িয়ে যান। যেসব রাস্তার জন্য রক্ষী বসানো হলো, তাদের কোনো দুটি একই মোড় ছোঁয় না — অর্থাৎ তারা একটি matching. আর কোনো রাস্তাকে আর যোগ করা যায় না (প্রতিটি বাকি রাস্তার এক প্রান্ত ইতিমধ্যে ব্যবহৃত), তাই এটি একটি maximal matching.
Pseudocode — vertex cover by maximal matching (any vertex numbering)
ApproxVertexCover(G):
C = { } // guarded junctions
for each edge (u, v) of G, in any order:
if u not in C and v not in C: // road still unguarded
C = C + { u, v } // guard BOTH ends
return C // |C| = 2 |M| <= 2 OPTরাস্তার ক্রম: A–B, A–C, B–C, B–D, C–E, D–E, D–F, E–G, F–G. নিচের ডায়াগ্রামে এগিয়ে দেখুন:
| দেখা রাস্তা | |
| সিদ্ধান্ত | |
| Matching M | |
| রক্ষী (cover) |
- শুরু. সাতটি মোড়, নয়টি রাস্তা, একজনও রক্ষী নেই। রাস্তাগুলো দেওয়া ক্রমে দেখা হবে।
- A–B. দুই প্রান্তই খালি। A আর B দুই জায়গাতেই রক্ষী বসান; A–B প্রথম matched রাস্তা। কোন প্রান্তটি “ঠিক”, তা আমরা জানি না — তাই দুটোই নিই। এটিই ২ গুণের উৎস।
- A–C, B–C, B–D. প্রতিটির এক প্রান্তে (A বা B) রক্ষী আছে, তাই তিনটিই ইতিমধ্যে পাহারায়। কিছু করার নেই।
- C–E. C আর E দুজনই খালি — দ্বিতীয় matched রাস্তা, রক্ষী এখন চারজন: A, B, C, E. খেয়াল করুন, A–B আর C–E কোনো মোড় ভাগ করে না।
- D–E, D–F. D–E-এর E প্রান্তে রক্ষী আছে, বাদ। D–F-এর দুই প্রান্তই খালি — তৃতীয় matched রাস্তা, রক্ষী ছয়জন।
- E–G, F–G. দুটোরই এক প্রান্তে (E আর F-এ) রক্ষী আছে। সব রাস্তা দেখা শেষ: cover = {A, B, C, D, E, F}, আকার ২ × ৩ = ৬. G-তে রক্ষী লাগেনি।
- তুলনা. Brute force বলে সর্বোত্তম ৪ (যেমন A, B, E, F). হলুদ তিনটি matched রাস্তা কোনো মোড় ভাগ করে না, তাই যেকোনো cover-এ তাদের জন্য তিনটি আলাদা রক্ষী লাগে: OPT ≥ ৩. তাই ৬ ≤ ২ × ৩ ≤ ২ × OPT. আসল ratio এখানে ৬ / ৪ = ১.৫.
কেন ফলটি সর্বোচ্চ দ্বিগুণ — প্রমাণ
ApproxVertexCover একটি vertex cover দেয়, যার আকার ≤ 2 · OPT, আর এটি O(V + E) সময়ে চলে।
- এটি cover: ধরুন শেষে কোনো edge (u, v) ঢাকা পড়েনি — u, v কেউই C-তে নেই। C শুধু বড় হয়, তাই যখন (u, v) দেখা হয়েছিল তখনও তারা C-তে ছিল না, আর তখনই অ্যালগরিদম দুটোকে যোগ করত। বিরোধ।
- নিম্নসীমা: যে edge-গুলোর জন্য রক্ষী বসানো হয়েছে, তাদের সেট M একটি matching — নতুন edge তখনই নেওয়া হয়, যখন তার দুই প্রান্ত আগের কোনো matched edge-এ নেই। যেকোনো vertex cover-কে M-এর প্রতিটি edge-এর অন্তত এক প্রান্ত নিতে হয়, আর এই প্রান্তগুলো সব আলাদা (M-এর edge কোনো vertex ভাগ করে না)। তাই OPT ≥ |M|.
- উপসংহার: |C| = 2|M| ≤ 2 · OPT. ∎
প্রতিটি edge একবার দেখা আর একটি boolean অ্যারে, তাই সময় Θ(V + E), বাড়তি জায়গা Θ(V). বাউন্ডটি tight: একটি তারার মতো গ্রাফে (কেন্দ্রে একটি মোড়, চারপাশে পাঁচটি) প্রথম রাস্তাতেই কেন্দ্র আর একটি পাতা নেওয়া হয় — ফল ২, অথচ শুধু কেন্দ্রই যথেষ্ট ছিল (OPT = ১)। সম্পূর্ণ bipartite গ্রাফ Kn,n-এ যেকোনো maximal matching perfect, তাই সেখানে ফল সবসময় 2n, অথচ OPT = n.
“সবচেয়ে বেশি রাস্তার মোড় আগে” কেন যথেষ্ট নয়: শুনতে বেশি চালাক লাগে — প্রতিবার সবচেয়ে বেশি ঢাকা-না-পড়া রাস্তা ছোঁয় এমন মোড়ে রক্ষী বসান। কিন্তু এর জন্য কোনো ধ্রুবক ratio নেই: বিশেষভাবে বানানো bipartite গ্রাফের একটি পরিবার আছে, যেখানে এই greedy সর্বোত্তমের প্রায় ln n গুণ রক্ষী বসায়। (এটি আসলে greedy set cover-এর একটি বিশেষ রূপ, তাই H(n)-এর বেশি খারাপ হয় না।) আর “প্রতিটি খালি রাস্তার যেকোনো এক প্রান্ত নিন” আরও খারাপ — তারায় প্রতিবার পাতা বেছে নিলে n − 1 জন, যেখানে ১ জনই যথেষ্ট। দুই প্রান্তই নেওয়ার “অপচয়”-টিই প্রমাণকে সম্ভব করে।
একটি সূক্ষ্ম পার্থক্য মনে রাখুন: maximal matching (আর বাড়ানো যায় না) লোভী এক পাসেই পাওয়া যায়; maximum matching (সবচেয়ে বড়) খুঁজতে পর্ব ৪৭-এর মতো augmenting path লাগে। Approximation-এর জন্য maximal-ই যথেষ্ট। Bipartite গ্রাফে আবার vertex cover নিখুঁতভাবেই সহজ — König-এর উপপাদ্য অনুযায়ী সেখানে সবচেয়ে ছোট cover = maximum matching-এর আকার।
৪ · কোডে vertex cover — approximation বনাম brute force
একটি approximation অ্যালগরিদম যাচাইয়ের সবচেয়ে সৎ উপায়: ছোট ইনপুটে brute force দিয়ে আসল OPT বের করে ratio ছাপানো। নিচের প্রোগ্রাম দুটি গ্রাফে এটি করে — আমাদের শহর আর পাঁচ পাতার একটি তারা। Brute force সব 2n টি vertex-উপসেট দেখে (n ≤ ২০ পর্যন্ত চলে); একটি উপসেট বিটমাস্ক হিসেবে রাখা, বিট v = 1 মানে মোড় v-তে রক্ষী।
#include <bit>
#include <cstdio>
#include <utility>
#include <vector>
using namespace std;
using Edges = vector<pair<int, int>>;
Edges matchingCover(int n, const Edges& es) { // 2-approximation
vector<bool> taken(n, false); Edges matched;
for (auto [u, v] : es)
if (!taken[u] && !taken[v]) { // road still unguarded: guard both ends
taken[u] = taken[v] = true; matched.push_back({u, v});
}
return matched;
}
unsigned exactCover(int n, const Edges& es) { // brute force over all 2^n subsets
unsigned best = (1u << n) - 1;
for (unsigned m = 0; m < (1u << n); ++m) {
if (popcount(m) >= popcount(best)) continue;
bool ok = true;
for (auto [u, v] : es) if (!(m >> u & 1) && !(m >> v & 1)) { ok = false; break; }
if (ok) best = m;
}
return best;
}
void report(const char* name, int n, const Edges& es) {
Edges matched = matchingCover(n, es); unsigned best = exactCover(n, es);
int size = 2 * int(matched.size()), opt = popcount(best);
printf("%s: matched", name);
for (auto [u, v] : matched) printf(" %c-%c", 'A' + u, 'A' + v);
printf("\n optimum:");
for (int v = 0; v < n; ++v) if (best >> v & 1) printf(" %c", 'A' + v);
printf("\n cover size %d, optimum %d, ratio %.2f\n", size, opt, double(size) / opt);
}
int main() {
Edges town = {{0,1}, {0,2}, {1,2}, {1,3}, {2,4}, {3,4}, {3,5}, {4,6}, {5,6}};
Edges star = {{0,1}, {0,2}, {0,3}, {0,4}, {0,5}};
report("town", 7, town);
report("star", 6, star);
}আউটপুট:
town: matched A-B C-E D-F
optimum: A B E F
cover size 6, optimum 4, ratio 1.50
star: matched A-B
optimum: A
cover size 2, optimum 1, ratio 2.00
শহরের ফল ডায়াগ্রামের সাথে মেলে: তিনটি matched রাস্তা, ছয়জন রক্ষী, সর্বোত্তম চারজন, ratio ১.৫০. তারায় ratio ঠিক ২.০০ — উপপাদ্যের সীমা পর্যন্ত, অর্থাৎ বাউন্ডটি এর চেয়ে ভালো করা যায় না। matchingCover একবার edge-তালিকা ঘোরে, তাই Θ(V + E); exactCover Θ(2n · E) — n = ৭-এ ১২৮টি উপসেট, কিন্তু n = ৬০-এ শত শত বছর। popcount (C++20, <bit>) একটি সংখ্যার 1-বিট গোনে; যে উপসেটে বিট বেশি, তা আগেই বাদ দেওয়া হয়, ফলে অনেক উপসেটে edge-পরীক্ষাই লাগে না।
def matching_cover(n, edges): # 2-approximation
taken, matched = [False] * n, []
for u, v in edges:
if not taken[u] and not taken[v]: # road still unguarded: guard both ends
taken[u] = taken[v] = True
matched.append((u, v))
return matched
def exact_cover(n, edges): # brute force over all 2^n subsets
best = (1 << n) - 1
for m in range(1 << n):
if bin(m).count("1") < bin(best).count("1") and \
all(m >> u & 1 or m >> v & 1 for u, v in edges):
best = m
return best
def report(name, n, edges):
matched = matching_cover(n, edges)
best = exact_cover(n, edges)
size, opt = 2 * len(matched), bin(best).count("1")
print(f"{name}: matched", " ".join(f"{chr(65 + u)}-{chr(65 + v)}" for u, v in matched))
print(" optimum:", " ".join(chr(65 + v) for v in range(n) if best >> v & 1))
print(f" cover size {size}, optimum {opt}, ratio {size / opt:.2f}")
town = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 4), (3, 4), (3, 5), (4, 6), (5, 6)]
star = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
report("town", 7, town)
report("star", 6, star)আউটপুট C++-এর সাথে হুবহু এক:
town: matched A-B C-E D-F
optimum: A B E F
cover size 6, optimum 4, ratio 1.50
star: matched A-B
optimum: A
cover size 2, optimum 1, ratio 2.00
দুই ভাষাতেই উপসেট ছোট থেকে বড় ক্রমে দেখা হয়, আর শুধু কঠোরভাবে ছোট cover পেলে best বদলায় — তাই একাধিক সর্বোত্তম থাকলেও (যেমন B, C, D, G-ও চারজনের cover) দুটি প্রোগ্রাম একই উত্তর ছাপে। bin(m).count("1") Python-এ বিট গোনার সহজ উপায় (Python ৩.১০ থেকে m.bit_count()-ও আছে)। নিজের কোড যাচাইয়ের অভ্যাস করুন: শত শত এলোমেলো ছোট গ্রাফে ratio ছাপিয়ে দেখুন কখনো ২-এর বেশি হয় কি না — হলে কোথাও ভুল আছে।
৫ · Set cover — greedy আর ln n-এর নিশ্চয়তা
একটি মোবাইল অপারেটর একটি উপজেলার ১৪টি ইউনিয়নে নেটওয়ার্ক দিতে চায়। জরিপে পাঁচটি সম্ভাব্য টাওয়ার-জায়গা পাওয়া গেছে; প্রতিটি থেকে কোন কোন ইউনিয়ন ঢাকা যায়, তা জানা। সবচেয়ে কম কয়টি টাওয়ার বসালে সব ইউনিয়ন নেটওয়ার্ক পায়? একই প্রশ্ন আসে কোচিং সেন্টারের শাখা বসাতে, প্রতিটি রোগের জন্য অন্তত একটি পরীক্ষা থাকে এমন সবচেয়ে ছোট ল্যাব-প্যানেল বানাতে, বা সব ফিচার ছোঁয় এমন সবচেয়ে ছোট test suite বাছতে।
Set cover: একটি universe U (n টি উপাদান) আর U-এর উপসেটের একটি পরিবার S1, …, Sm দেওয়া, যাদের মিলন U. সবচেয়ে কম সংখ্যক সেট বাছুন, যাদের মিলন U. এটিও Karp-এর ১৯৭২ সালের তালিকায় আছে। Vertex cover এর একটি বিশেষ রূপ: উপাদান = edge, প্রতিটি vertex-এর সেট = তাকে ছোঁয়া edge-গুলো।
আমাদের উদাহরণ: ইউনিয়ন u1–u7 উত্তর দিকে, u8–u14 দক্ষিণে। টাওয়ার North ঢাকে u1–u7, South ঢাকে u8–u14, X ঢাকে u1–u4 আর u8–u11 (৮টি), Y ঢাকে u5, u6, u12, u13 (৪টি), Z ঢাকে u7 আর u14. স্পষ্টতই North + South = ২টি টাওয়ারই যথেষ্ট। Greedy কী করে দেখুন — নিয়ম: প্রতিবার সেই সেটটি নিন, যা সবচেয়ে বেশি এখনো-না-ঢাকা উপাদান ঢাকে।
Pseudocode — greedy set cover
GreedySetCover(U, S1..Sm):
covered = { }; picked = [ ]
while covered != U:
choose i maximising |Si - covered| // most NEW elements
k = |Si - covered|
every element of Si - covered pays price 1/k // for the proof only
covered = covered + Si; picked.append(i)
return picked| নতুন কয়টি ঢাকবে | |
| বাছাই | |
| টাওয়ার / মোট দাম |
- শুরু. কোনো ইউনিয়ন ঢাকা নেই। নতুন ঢাকার সংখ্যা: North ৭, South ৭, X ৮, Y ৪, Z ২. X একটু বেশি — greedy-র চোখে সেটিই সেরা।
- X নিল. ৮টি নতুন ইউনিয়ন — প্রতিটির দাম ১/৮, মোট ১. বাকি u5, u6, u7, u12, u13, u14. এখন North আর South প্রতিটি মাত্র ৩টি নতুন দেবে, কিন্তু Y দেবে ৪টি।
- Y নিল. ৪টি নতুন, প্রতিটির দাম ১/৪. বাকি শুধু u7 আর u14 — North বা South একটি করে, Z দুটোই।
- Z নিল. ২টি নতুন, দাম ১/২ করে। সব ঢাকা, greedy নিয়েছে ৩টি টাওয়ার, অথচ ২টিই যথেষ্ট ছিল। মোট দাম = ৩ = টাওয়ারের সংখ্যা — প্রতিটি বাছাই ঠিক ১ দাম ভাগ করে দেয়।
- দামের হিসাব. সর্বোত্তম সমাধানের North টাওয়ারের ৭টি ইউনিয়নের দাম যোগ করুন: ৪ × ১/৮ + ২ × ১/৪ + ১/২ = ১.৫, যা H(৭) = ১ + ১/২ + … + ১/৭ ≈ ২.৫৯-এর কম। South-ও ১.৫. সব দাম এই দুই সেটেই আছে: ৩ = ১.৫ + ১.৫ ≤ H(৭) × OPT. পরের অংশের প্রমাণ ঠিক এটিই সাধারণভাবে করে।
প্রমাণ — pricing যুক্তি
(Johnson ১৯৭৪, Lovász ১৯৭৫) Greedy set cover-এর উত্তর ≤ H(d) · OPT, যেখানে d = সবচেয়ে বড় সেটের আকার আর H(d) = 1 + 1/2 + … + 1/d হলো harmonic number. যেহেতু d ≤ n আর H(n) ≤ ln n + 1, তাই ratio ≤ ln n + 1.
প্রমাণের কৌশলটিকে বলে pricing (বা charging): greedy যখন একটি সেট নেয় যা k টি নতুন উপাদান ঢাকে, তখন সেই k টি উপাদানের প্রত্যেকের গায়ে ১/k দাম লিখে দিন। প্রতিটি বাছাই মোট ঠিক ১ দাম ভাগ করে, তাই greedy-র সেট-সংখ্যা = সব উপাদানের মোট দাম। এখন সর্বোত্তম সমাধানের যেকোনো একটি সেট S নিন, |S| = s. S-এর উপাদানগুলোকে greedy যে ক্রমে ঢেকেছে সেই ক্রমে সাজান: e1, e2, …, es. ej ঢাকার ঠিক আগে S-এর অন্তত s − j + 1 টি উপাদান (ej, …, es) এখনো খোলা — অর্থাৎ S নিজেই তখন অন্তত s − j + 1 টি নতুন উপাদান দিত। Greedy সবচেয়ে বেশি নতুন দেয় এমনটি বাছে, তাই সে অন্তত ততগুলো ঢেকেছে, আর ej-এর দাম ≤ 1/(s − j + 1). যোগ করলে S-এর মোট দাম ≤ 1/s + … + 1/2 + 1 = H(s) ≤ H(d). সর্বোত্তম সমাধানের OPT টি সেট মিলে প্রতিটি উপাদান অন্তত একবার ঢাকে, তাই মোট দাম ≤ OPT · H(d). ∎
Greedy কতটা খারাপ হতে পারে: আমাদের উদাহরণটি একটি পরিবারের সদস্য। সারির দৈর্ঘ্য 2k − 1 আর “ফাঁদ” সেটগুলোর আকার 2k, 2k−1, …, 2 (প্রতিটির অর্ধেক প্রতি সারিতে) নিলে greedy প্রতিবার ফাঁদ বাছে — মোট k টি সেট, অথচ OPT = ২. এখানে n = 2k+1 − 2, তাই ratio k/2 ≈ (log₂ n)/2 — log n হারে বাড়ে। অর্থাৎ greedy-র ক্ষেত্রে ln n-এর ধাঁচের সীমা শুধু প্রমাণের দুর্বলতা নয়, সত্যিই ঘটে।
জটিলতা: প্রতি রাউন্ডে সব সেটের “নতুন উপাদান” গুনতে O(Σ|Si|) লাগে, আর রাউন্ড সর্বোচ্চ min(m, n) টি — সরল বাস্তবায়নে O(min(m, n) · Σ|Si|). নিচের কোডে প্রতিটি সেট একটি ১৪-বিটের মাস্ক, তাই “নতুন কয়টি” = popcount(mask & ~covered). প্রতিটি সেটের খরচ আলাদা হলে (weighted set cover) নিয়মটি হয় “নতুন প্রতি উপাদানে সবচেয়ে কম খরচ” — Chvátal (১৯৭৯) দেখান সেখানেও একই H(d) নিশ্চয়তা খাটে।
#include <bit>
#include <cstdio>
#include <vector>
using namespace std;
int main() {
const char* name[] = {"North", "South", "X", "Y", "Z"}; // candidate towers
vector<vector<int>> covers = {{1, 2, 3, 4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14},
{1, 2, 3, 4, 8, 9, 10, 11}, {5, 6, 12, 13}, {7, 14}};
int k = int(covers.size());
vector<unsigned> mask(k, 0);
for (int i = 0; i < k; ++i) for (int u : covers[i]) mask[i] |= 1u << (u - 1);
const unsigned all = (1u << 14) - 1; // unions 1..14
unsigned covered = 0; int picks = 0;
while (covered != all) { // greedy: most new unions
int best = -1, gain = 0;
for (int i = 0; i < k; ++i) {
int g = popcount(mask[i] & ~covered);
if (g > gain) { gain = g; best = i; }
}
covered |= mask[best]; ++picks;
printf("pick %-5s: %d new unions, each pays 1/%d\n", name[best], gain, gain);
}
int opt = k; unsigned optSet = 0; // exact: try all 2^k choices
for (unsigned s = 1; s < (1u << k); ++s) {
unsigned u = 0;
for (int i = 0; i < k; ++i) if (s >> i & 1) u |= mask[i];
if (u == all && popcount(s) < opt) { opt = popcount(s); optSet = s; }
}
double h = 0;
for (int i = 1; i <= 8; ++i) h += 1.0 / i; // H(largest set size)
printf("greedy %d towers, optimum %d:", picks, opt);
for (int i = 0; i < k; ++i) if (optSet >> i & 1) printf(" %s", name[i]);
printf("\nratio %.2f, guarantee H(8) = %.2f\n", double(picks) / opt, h);
}আউটপুট:
pick X : 8 new unions, each pays 1/8
pick Y : 4 new unions, each pays 1/4
pick Z : 2 new unions, each pays 1/2
greedy 3 towers, optimum 2: North South
ratio 1.50, guarantee H(8) = 2.72
তিনটি pick লাইন ডায়াগ্রামের তিনটি বাছাই। শেষ অংশ সব 25 − 1 = ৩১টি অশূন্য টাওয়ার-সমষ্টি দেখে সর্বোত্তম ২ বের করে। এখানে d = ৮ (X-এর আকার), তাই উপপাদ্যের নিশ্চয়তা ratio ≤ H(৮) ≈ ২.৭২; আসল ratio ১.৫০ তার অনেক নিচে। if (g > gain)-এ কঠোর তুলনা মানে সমান হলে আগের index জেতে — tie-breaking নির্দিষ্ট রাখলে আউটপুট সব মেশিনে একই হয়। 1u << (u - 1): ইউনিয়ন ১ থাকে বিট ০-তে — off-by-one এড়াতে এমন রূপান্তর এক জায়গায় রাখুন।
৬ · Metric TSP — MST দ্বিগুণ করে ঘোরা, তারপর শর্টকাট
একজন ডেলিভারি রাইডার গুদাম A থেকে বের হয়ে পাঁচটি ঠিকানায় (B–F) পার্সেল দিয়ে আবার A-তে ফিরবেন — সবচেয়ে কম পথ চালিয়ে। এটি traveling salesman problem (TSP). শহরের রাস্তা গ্রিডের মতো, তাই দুই ঠিকানার দূরত্ব = কয়টি ব্লক পূর্ব-পশ্চিমে + কয়টি উত্তর-দক্ষিণে: d = |x₁ − x₂| + |y₁ − y₂| (Manhattan দূরত্ব)। ঠিকানা: A(6, 0), B(6, 4), C(0, 4), D(2, 4), E(5, 0), F(1, 0).
এই দূরত্ব একটি গুরুত্বপূর্ণ শর্ত মানে — ত্রিভুজ অসমতা: যেকোনো u, v, w-এর জন্য d(u, w) ≤ d(u, v) + d(v, w); সরাসরি যাওয়া কখনো মাঝে থেমে যাওয়ার চেয়ে দীর্ঘ নয়। দূরত্ব প্রতিসম আর এই অসমতা মানলে সমস্যাটিকে বলে metric TSP. সমতলের সরলরেখা-দূরত্ব, Manhattan দূরত্ব, আর যেকোনো রাস্তার নেটওয়ার্কে সবচেয়ে ছোট পথের দূরত্ব (পর্ব ৪৪-এর Floyd–Warshall থেকে পাওয়া) — সবই metric. Metric TSP-ও NP-hard, কিন্তু এখানে approximation সম্ভব।
Pseudocode — MST doubling (2-approximation for metric TSP), start = depot
ApproxTSP(points, d):
T = minimum spanning tree of the complete graph with weights d // Prim, O(n^2)
order = preorder DFS walk of T from the depot // first visits only
return order + [depot] // close the tour| এই ধাপ | |
| ক্রম | |
| দৈর্ঘ্য |
- ঠিকানাগুলো. ছয়টি বিন্দু, প্রতিটি জোড়ার মধ্যে ব্লক-দূরত্ব। যেকোনো ট্যুর ছয়টি রাস্তা নেয়; A থেকে শুরু ধরে আলাদা ট্যুর (৬ − ১)! / ২ = ৬০টি — এখানে গোনা যায়, ৩০টি ঠিকানায় যায় না।
- MST. সম্পূর্ণ গ্রাফে Prim (পর্ব ৪৫) A থেকে: A–E (১), A–B (৪), B–D (৪), D–C (২), E–F (৪) — মোট ১৫. সর্বোত্তম ট্যুর থেকে একটি রাস্তা মুছলে একটি spanning path থাকে, যা নিজেই একটি spanning tree, তাই OPT ≥ ১৫.
- দুবার হাঁটা. A থেকে DFS: সন্তানদের অক্ষরের ক্রমে নামুন, শেষ হলে একই রাস্তায় ফিরুন। A B D C D B A E F E A — প্রতিটি MST-রাস্তা ঠিক দুবার, দৈর্ঘ্য ২ × ১৫ = ৩০. এটি একটি বৈধ বৃত্তাকার হাঁটা, তবে D, B, A, E একাধিকবার আসে।
- শর্টকাট. প্রতিটি ঠিকানা প্রথমবার দেখার সময়টুকু রাখুন: A B D C E F, তারপর A. C থেকে D–B–A ঘুরে E-তে যাওয়ার (২ + ৪ + ৪ + ১ = ১১) বদলে সরাসরি C → E (৯); F → E → A (৪ + ১ = ৫)-এর বদলে F → A (৫)। ত্রিভুজ অসমতার জন্য শর্টকাট কখনো দীর্ঘ হয় না। ট্যুর ২৮.
- তুলনা. সব ৬০টি ট্যুর দেখলে সর্বোত্তম A B D C F E A = ৪ + ৪ + ২ + ৫ + ৪ + ১ = ২০. শৃঙ্খলটি: MST ১৫ ≤ OPT ২০ ≤ ALG ২৮ ≤ ২ × MST ৩০. Ratio ২৮ / ২০ = ১.৪০ — নিশ্চয়তা ২-এর মধ্যে।
কেন ২-approximation
- OPT ≥ w(MST): সর্বোত্তম ট্যুর থেকে যেকোনো একটি edge মুছুন — যা থাকে তা সব শহর ছোঁয়া একটি path, অর্থাৎ একটি spanning tree. দূরত্ব ঋণাত্মক নয়, তাই OPT ≥ সেই tree-র ওজন ≥ w(MST).
- পুরো হাঁটা = 2 · w(MST): DFS প্রতিটি tree-edge-এ একবার নামে, একবার ওঠে।
- শর্টকাট দীর্ঘ করে না: হাঁটার মধ্যে u → … → w অংশ (মাঝের সবাই আগে দেখা) বদলে সরাসরি u → w নিলে, ত্রিভুজ অসমতা বারবার প্রয়োগ করে d(u, w) ≤ মাঝের পথের দৈর্ঘ্য। এই কাজকে বলে shortcutting, আর ফল হলো MST-এর preorder ক্রম।
- তাই ALG ≤ 2 · w(MST) ≤ 2 · OPT. ∎
জটিলতা: সম্পূর্ণ গ্রাফে E = n(n − 1)/2, তাই অ্যারে-ভিত্তিক Prim Θ(n²), DFS Θ(n) — মোট Θ(n²) সময়। দূরত্ব দরকারমতো হিসাব করলে বাড়তি জায়গা Θ(n), পূর্ণ দূরত্ব-ম্যাট্রিক্স রাখলে Θ(n²).
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <numeric>
#include <vector>
using namespace std;
struct Pt { int x, y; };
vector<Pt> P = {{6,0}, {6,4}, {0,4}, {2,4}, {5,0}, {1,0}}; // A..F, A = depot
int d(int i, int j) { return abs(P[i].x - P[j].x) + abs(P[i].y - P[j].y); } // street blocks
int cost(const vector<int>& t) { // closed tour length
int s = 0;
for (size_t i = 0; i < t.size(); ++i) s += d(t[i], t[(i + 1) % t.size()]);
return s;
}
int main() {
int n = int(P.size()), mst = 0;
vector<int> key(n, 1 << 30), par(n, -1); vector<bool> in(n, false);
vector<vector<int>> kids(n); key[0] = 0;
for (int it = 0; it < n; ++it) { // dense Prim, O(n^2)
int u = -1;
for (int v = 0; v < n; ++v) if (!in[v] && (u < 0 || key[v] < key[u])) u = v;
in[u] = true; mst += key[u];
if (par[u] >= 0) { kids[par[u]].push_back(u); printf("MST edge %c-%c %d\n", 'A' + par[u], 'A' + u, key[u]); }
for (int v = 0; v < n; ++v) if (!in[v] && d(u, v) < key[v]) { key[v] = d(u, v); par[v] = u; }
}
for (auto& k : kids) sort(k.begin(), k.end()); // visit children A..F order
vector<int> tour; // preorder walk = shortcut tour
auto dfs = [&](auto&& self, int u) -> void {
tour.push_back(u);
for (int c : kids[u]) self(self, c);
}; dfs(dfs, 0);
vector<int> best(n); iota(best.begin(), best.end(), 0); int opt = cost(best);
vector<int> t = best; // exact: all (n-1)! tours from A
while (next_permutation(t.begin() + 1, t.end())) if (cost(t) < opt) { opt = cost(t); best = t; }
auto show = [](const char* s, const vector<int>& t) {
printf("%s", s); for (int v : t) printf("%c", 'A' + v); printf("A\n"); };
printf("MST = %d, doubled walk = %d\n", mst, 2 * mst);
show("MST tour ", tour); printf(" length %d\n", cost(tour));
show("optimum ", best); printf(" length %d, ratio %.2f\n", opt, double(cost(tour)) / opt);
}আউটপুট:
MST edge A-E 1
MST edge A-B 4
MST edge B-D 4
MST edge D-C 2
MST edge E-F 4
MST = 15, doubled walk = 30
MST tour ABDCEFA
length 28
optimum ABDCFEA
length 20, ratio 1.40
Prim-এর লুপে key[v] < key[u] কঠোর, তাই সমান key (B আর F দুজনেরই ৪) হলে ছোট index জেতে — প্রথমে B. সন্তানদের তালিকা sort করায় DFS অক্ষরের ক্রমে নামে, ফলে ট্যুর ABDCEFA, ডায়াগ্রামের সাথে হুবহু। DFS-টি একটি recursive lambda (self নিজেকেই ডাকে)। Brute force-এ A-কে প্রথমে স্থির রেখে next_permutation বাকি পাঁচটির ১২০টি বিন্যাস দেখে — প্রতিটি ট্যুর দুই দিক থেকে দুবার আসে, তাই আলাদা ট্যুর ৬০টি। ১৫টি ঠিকানাতেই ১৪! ≈ ৮.৭ × ১০১০ টি বিন্যাস — brute force সেখানেই অচল, আর MST-ট্যুর তখনো মিলিসেকেন্ডে আসে।
Christofides — ২ থেকে ১.৫
দুবার হাঁটার অপচয় কোথায়? প্রতিটি tree-edge দুবার চলা — শুধু যাতে প্রতিটি vertex-এর degree জোড় হয় আর একটি Euler circuit (প্রতিটি edge ঠিক একবার নিয়ে ঘোরা) পাওয়া যায়। Christofides-এর অ্যালগরিদম আরও সস্তায় degree জোড় করে: (১) MST T বানান; (২) T-তে বিজোড় degree-র vertex-গুলো O নিন — handshake lemma অনুযায়ী এদের সংখ্যা জোড়; (৩) O-এর ওপর সবচেয়ে কম ওজনের perfect matching M বের করুন; (৪) T + M-এ সব degree জোড়, তাই Euler circuit আছে; (৫) সেটি ধরে হাঁটুন আর শর্টকাট নিন।
কেন ১.৫: সর্বোত্তম ট্যুরকে শুধু O-এর vertex-গুলোতে শর্টকাট করুন — ত্রিভুজ অসমতায় দৈর্ঘ্য ≤ OPT. এই ছোট ট্যুরের edge-গুলো একটি-বাদ-একটি নিলে O-এর দুটি perfect matching পাওয়া যায়, যাদের মোট ≤ OPT, তাই হালকাটি ≤ OPT/2, আর w(M) ≤ OPT/2. ফলে ALG ≤ w(T) + w(M) ≤ OPT + OPT/2 = 1.5 · OPT. সময়ের প্রধান অংশ সাধারণ গ্রাফে minimum-weight perfect matching, O(n³). Nicos Christofides ১৯৭৬ সালে এটি প্রকাশ করেন; সোভিয়েত গবেষক Anatoliy Serdyukov স্বাধীনভাবে একই অ্যালগরিদম পান (১৯৭৮ সালে প্রকাশ), তাই এখন অনেকে একে Christofides–Serdyukov বলেন। প্রায় ৪৫ বছর কেউ ১.৫ ভাঙতে পারেননি। ২০২০ সালে Anna Karlin, Nathan Klein আর Shayan Oveis Gharan একটি randomized অ্যালগরিদমে দেখান ১.৫ − ε সম্ভব, যেখানে ε অবিশ্বাস্য রকম ছোট (প্রায় ১০−৩৬) — ব্যবহারিক নয়, কিন্তু তাত্ত্বিক “দেয়াল” যে অভেদ্য নয়, তা প্রমাণিত।
ত্রিভুজ অসমতা না থাকলে — কোনো ধ্রুবক ratio নেই
সাধারণ TSP-এ (দূরত্ব যেকোনো) কোনো ধ্রুবক ρ-approximation নেই, যদি না P = NP (Sahni ও Gonzalez, ১৯৭৬)। প্রমাণ একটি reduction: ধরুন একটি ρ-approximation আছে। একটি গ্রাফ G (n vertex) নিন, যেখানে প্রশ্ন — Hamiltonian cycle (পর্ব ৫৬) আছে কি না, যা NP-complete. সম্পূর্ণ গ্রাফ বানান: G-এর edge-এর ওজন ১, বাকি জোড়ার ওজন ρn + 1. G-তে Hamiltonian cycle থাকলে OPT = n, তাই অ্যালগরিদম ≤ ρn দৈর্ঘ্যের ট্যুর দেবে — যাতে একটিও ভারী edge নেই — একটি থাকলেই দৈর্ঘ্য ρn ছাড়িয়ে যেত। না থাকলে প্রতিটি ট্যুরে অন্তত একটি ভারী edge, দৈর্ঘ্য > ρn. অর্থাৎ অ্যালগরিদমের ফল ≤ ρn কি না দেখেই Hamiltonian cycle-এর প্রশ্নের উত্তর polynomial সময়ে মিলত — P = NP. এই ধরনের যুক্তিকে বলে gap reduction: “হ্যাঁ” আর “না” ইনপুটের OPT-এর মধ্যে এত বড় ফাঁক তৈরি করা, যে ভালো approximation-ই সিদ্ধান্ত দিয়ে দেয়। খেয়াল করুন, ভারী edge-গুলো ত্রিভুজ অসমতা ভাঙে — metric শর্তই approximation-কে সম্ভব করে।
৭ · Load balancing আর knapsack — PTAS, FPTAS
List scheduling — Graham-এর ২ − ১/m
একটি পোশাক কারখানায় m টি সেলাই লাইন আর n টি অর্ডার; অর্ডার j শেষ করতে pj ঘণ্টা লাগে, আর একটি অর্ডার একটিই লাইনে হয়। লক্ষ্য: সবচেয়ে ব্যস্ত লাইনের মোট সময় — makespan — যতটা সম্ভব কম রাখা। m = ২ হলেই এটি partition সমস্যা, NP-hard. Ronald Graham-এর ১৯৬৬ সালের list scheduling: অর্ডারগুলো যে ক্রমে আসে, প্রতিটি সেই মুহূর্তের সবচেয়ে কম ব্যস্ত লাইনে দিন। একটি min-heap-এ (পর্ব ২৩) লাইনের বোঝা রাখলে সময় O(n log m).
প্রমাণ: দুটি নিম্নসীমা — OPT ≥ maxj pj (সবচেয়ে বড় অর্ডারটি কোনো লাইনে যাবেই) আর OPT ≥ (Σ pj)/m (মোট কাজ m লাইনে ভাগ হলে কাউকে অন্তত গড়টুকু করতে হয়)। যে অর্ডার j সবার শেষে শেষ হয়, সেটি যখন দেওয়া হয়েছিল তখন তার লাইনটি সবচেয়ে কম ব্যস্ত ছিল, তাই তার শুরুর সময় ≤ (Σ p − pj)/m. ফলে makespan ≤ (Σ p)/m + pj(1 − 1/m) ≤ OPT + (1 − 1/m) · OPT = (2 − 1/m) · OPT. ∎ বড় অর্ডারগুলো আগে দিলে (Longest Processing Time first, LPT) Graham ১৯৬৯ সালে দেখান ratio ≤ 4/3 − 1/(3m); দাম শুধু একটি সর্ট, O(n log n).
Knapsack — যেকোনো কাছাকাছি, ইচ্ছেমতো
কিছু সমস্যায় আমরা আরও ভালো পাই: আপনি নিজে ε বেছে দিন (যেমন ০.০১), আর অ্যালগরিদম (1 + ε)-approximation দেবে — maximization-এ অন্তত (1 − ε) · OPT মানের উত্তর। ε যত ছোট, সময় তত বেশি। দুটি স্তর:
- PTAS (polynomial-time approximation scheme): প্রতিটি স্থির ε-এর জন্য সময় n-এর polynomial — কিন্তু ε-এর ওপর যেকোনোভাবে নির্ভর করতে পারে, যেমন O(n1/ε). ε = ০.০১ হলে n100 — তত্ত্বে polynomial, বাস্তবে অচল।
- FPTAS (fully polynomial): সময় n আর 1/ε দুটোরই polynomial, যেমন O(n³/ε).
0/1 knapsack-এর (পর্ব ৫৩) জন্য Ibarra আর Kim ১৯৭৫ সালে প্রথম FPTAS দেন। ধারণা: “মোট মান v-এর জন্য সবচেয়ে কম ওজন” — মান-ভিত্তিক এই DP-র সময় O(n · Σv), যা pseudo-polynomial; মানগুলো বড় হলেই সমস্যা। তাই মানগুলো ছোট করুন: K = ε · vmax / n নিন, প্রতিটি মান vi-কে বদলে ⌊vi / K⌋ করুন (সবচেয়ে বড়টি এখন ≤ n/ε), নতুন মানে DP চালিয়ে সেরা সেট নিন। সময় O(n · n · n/ε) = O(n³/ε). নিশ্চয়তা: গোল করায় প্রতিটি জিনিস সর্বোচ্চ K মান হারায়, তাই সর্বোত্তম সেটটি নতুন হিসাবে সর্বোচ্চ nK = ε · vmax হারায়। আমাদের সেটটি নতুন হিসাবে অন্তত তার সমান ভালো, তাই আসল মানে ALG ≥ OPT − ε · vmax ≥ (1 − ε) · OPT. এখানে ধরে নেওয়া হয়েছে প্রতিটি জিনিস একা ব্যাগে ধরে, তাই OPT ≥ vmax. ∎
উল্টো দিকে, যে সমস্যা ছোট সংখ্যাতেও NP-hard (strongly NP-hard, যেমন bin packing বা সাধারণ m-এর scheduling), তাদের FPTAS নেই, যদি না P = NP — কারণ FPTAS দিয়ে pseudo-polynomial নিখুঁত অ্যালগরিদম বানানো যেত।
আরও গভীরে — LP relaxation আর rounding: Vertex cover-কে integer program হিসেবে লিখুন: প্রতিটি vertex-এর জন্য xv ∈ {0, 1}, প্রতিটি edge-এ xu + xv ≥ 1, Σ wvxv কমান। “০ বা ১” শর্ত তুলে 0 ≤ xv ≤ 1 করলে এটি LP relaxation, যা polynomial সময়ে সমাধান হয়, আর LP ≤ OPT. এখন rounding: xv ≥ 1/2 হলে v নিন। প্রতিটি edge-এ দুই মানের যোগ ≥ ১, তাই অন্তত একটি ≥ ১/২ — এটি cover; আর প্রতিটি নেওয়া vertex-এর খরচ ≤ 2wvxv, তাই মোট ≤ 2 · LP ≤ 2 · OPT. সুবিধা: এটি ওজনযুক্ত vertex cover-এও কাজ করে, যেখানে matching-কৌশল খাটে না (Hochbaum, ১৯৮২)। এই LP-র ভেতরে একটি সীমাও আছে — Kn-এ সব x = 1/2 দিলে LP = n/2, অথচ OPT = n − 1, তাই এই LP দিয়ে ২-এর চেয়ে ভালো প্রমাণ করা যায় না (integrality gap ≈ ২)।
৮ · কতটা কাছে যাওয়া অসম্ভব — inapproximability আর ইতিহাস
প্রতিটি NP-hard সমস্যা approximation-এর দিক থেকে সমান নয়। কিছুতে যত কাছে চান যাওয়া যায়, কিছুতে একটি সীমার নিচে নামা নিজেই NP-hard — এই দ্বিতীয় ধরনের ফলকে বলে inapproximability. মোটামুটি একটি সিঁড়ি:
| স্তর | উদাহরণ | জানা সীমা (P ≠ NP ধরে) |
|---|---|---|
| FPTAS | 0/1 knapsack | যেকোনো ε, সময় poly(n, 1/ε) |
| PTAS | সমতলের (Euclidean) TSP — Arora (১৯৯৬), Mitchell স্বাধীনভাবে | যেকোনো স্থির ε |
| ধ্রুবক ratio | Vertex cover ২, metric TSP ১.৫, list scheduling | Vertex cover-এ প্রায় ১.৪১ (√২)-এর নিচে NP-hard; metric TSP-এ ১২৩/১২২-এর নিচে NP-hard |
| log n | Set cover — greedy H(n) | (1 − ε) ln n-এর নিচে NP-hard |
| কোনো ধ্রুবক নয় | সাধারণ TSP | কোনো ধ্রুবক ρ নয় |
এই ধরনের নিম্নসীমার বেশিরভাগের উৎস PCP theorem (১৯৯২; Arora ও Safra, এবং Arora, Lund, Motwani, Sudan ও Szegedy): NP-এর প্রতিটি “হ্যাঁ”-উত্তরের প্রমাণকে এমনভাবে লেখা যায়, যে একজন যাচাইকারী এলোমেলোভাবে মাত্র কয়েকটি (ধ্রুবক সংখ্যক) বিট পড়ে উচ্চ সম্ভাবনায় ভুল প্রমাণ ধরতে পারেন। এর একটি সমতুল্য রূপ: MAX-3SAT-এ (যত বেশি clause সত্য করা যায়) একটি নির্দিষ্ট ধ্রুবকের চেয়ে ভালো approximation NP-hard — তাই MAX-3SAT-এর PTAS নেই, যদি না P = NP. সেখান থেকে gap-রক্ষাকারী reduction দিয়ে অন্য সমস্যায় সীমা আসে। যে সমস্যায় কোনো ধ্রুবকের নিচে যাওয়া NP-hard, তাকে বলে APX-hard — vertex cover আর metric TSP দুটোই। Set cover-এ Feige (১৯৯৮) আর পরে Dinur ও Steurer (২০১৪) দেখান (1 − ε) ln n-এর চেয়ে ভালো করা কঠিন — অর্থাৎ ১৯৭৪ সালের সরল greedy প্রায় সেরা যা সম্ভব। Vertex cover-এ ২-এর চেয়ে ভালো কোনো অ্যালগরিদম আজও জানা নেই; Unique Games Conjecture (Khot, ২০০২) সত্য হলে ২ − ε-ও অসম্ভব (Khot ও Regev, ২০০৮)।
সংক্ষিপ্ত ইতিহাস: ১৯৬৬ সালে Bell Labs-এ Ronald Graham multiprocessor scheduling-এর list scheduling বিশ্লেষণ করে প্রথম দিকের একটি প্রমাণিত worst-case ratio দেন — NP-completeness তত্ত্ব আসার আগেই। ১৯৭১–৭২ সালে Cook আর Karp NP-completeness দাঁড় করানোর পর প্রশ্ন জাগে: নিখুঁত না হলে কতটা কাছে? ১৯৭৪ সালে David S. Johnson-এর “Approximation algorithms for combinatorial problems” প্রবন্ধ set cover-এর greedy-সহ কয়েকটি সমস্যার ratio বিশ্লেষণ করে — এটিকে ক্ষেত্রটির ভিত্তি-প্রবন্ধগুলোর একটি ধরা হয়। ১৯৭৫-এ Ibarra–Kim-এর knapsack FPTAS, ১৯৭৬-এ Sahni–Gonzalez-এর সাধারণ TSP-র অসম্ভবতা আর Christofides-এর ১.৫. Vertex cover-এর matching-অ্যালগরিদমটি সাধারণত Gavril-এর নামে উল্লেখ করা হয় (Garey ও Johnson-এর ১৯৭৯ সালের বিখ্যাত বইয়ে)। ১৯৯২-এর PCP theorem ক্ষেত্রটিকে উল্টো দিকের প্রমাণের হাতিয়ার দেয়।
পরীক্ষা ও ইন্টারভিউতে:
- Approximation ratio-র সংজ্ঞা দিন। — Minimization-এ প্রতিটি ইনপুটে ALG ≤ ρ · OPT; maximization-এ ALG ≥ OPT/ρ; সাথে polynomial সময়।
- Vertex cover-এর ২-approximation লিখে প্রমাণ করুন। — Maximal matching M-এর সব প্রান্ত নিন; যেকোনো cover-এ M-এর প্রতিটি edge-এর আলাদা একটি প্রান্ত লাগে, তাই 2|M| ≤ 2 · OPT.
- Metric TSP-এ MST কেন নিম্নসীমা? — ট্যুর থেকে একটি edge মুছলে spanning tree; শর্টকাট ত্রিভুজ অসমতায় দীর্ঘ হয় না, তাই ALG ≤ 2 · MST ≤ 2 · OPT.
- Greedy set cover-এর নিশ্চয়তা কত? — H(n) ≤ ln n + 1; pricing যুক্তি, আর (1 − ε) ln n-এর চেয়ে ভালো করা NP-hard.
- PTAS আর FPTAS-এর পার্থক্য? — দুটোই যেকোনো ε-তে (1 + ε); FPTAS-এর সময় 1/ε-এরও polynomial (knapsack: O(n³/ε)), PTAS-এর নাও হতে পারে (n1/ε)।
৯ · AI-এর যুগে approximation — ratio দাবি করা সহজ, প্রমাণ করা কঠিন
AI সহকারীকে “vertex cover-এর approximation দাও” বললে সে প্রায়ই সুন্দর কোড আর আত্মবিশ্বাসী একটি ratio লিখে দেবে। সমস্যা হলো, approximation-এর পুরো মূল্য প্রমাণে — আর ঠিক সেখানেই AI সবচেয়ে বেশি ভুল করে: degree-greedy-কে “২-approximation” বলা, MST-ট্যুরকে ত্রিভুজ অসমতা ছাড়াই খাটানো, বা “Christofides O(n²)” লিখে matching-এর খরচ ভুলে যাওয়া।
| AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|
| “বেশি degree আগে” greedy-কে ২-approximation বলা | প্রমাণ চান; নিম্নসীমাটি কী, জিজ্ঞেস করুন — উত্তর না এলে দাবিটি অপ্রমাণিত |
| Matching-অ্যালগরিদমে edge-এর এক প্রান্ত নেওয়া | তারা-গ্রাফে চালান: পাতা বাছলে n − 1, যেখানে OPT ১ |
| দূরত্ব metric কি না না দেখে MST-ট্যুর চালানো | সব ত্রয়ী (u, v, w)-তে d(u, w) ≤ d(u, v) + d(v, w) পরীক্ষা করুন |
| Maximization-এ ratio উল্টো লেখা | সংজ্ঞা লিখে মিলিয়ে দেখুন — ALG ≥ OPT/ρ |
| উদাহরণে ratio ১.২ দেখে “ratio ১.২” দাবি করা | একটি উদাহরণ নিশ্চয়তা নয়; worst-case পরিবার খুঁজতে বলুন |
AI-কে সমাধান লিখতে না বলে, আপনার প্রমাণ আর পরীক্ষা যাচাই করতে বলুন:
Approximation কোড যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১১৯৬৬ সালে Ronald Graham-এর scheduling বিশ্লেষণে একটি অদ্ভুত জিনিস ধরা পড়েছিল — দ্রুত মেশিন বা বেশি মেশিন দিলেও কাজ দেরিতে শেষ হতে পারে। এই “অদ্ভুততা” থেকে approximation-এর চিন্তা কীভাবে এল?
Ronald Graham তখন Bell Labs-এ কাজ করতেন, যেখানে অনেক প্রসেসর মিলে একটি কাজ ভাগ করে করার প্রশ্ন বাস্তব হয়ে উঠছিল। তাঁর ১৯৬৬ সালের প্রবন্ধের শিরোনামেই ছিল “multiprocessing anomalies” — অসংগতি। তিনি দেখান, list scheduling-এ (কাজগুলোর মধ্যে আগে-পরে নির্ভরতা থাকলে) প্রসেসর বাড়ালে, কাজের সময় কমালে, বা নির্ভরতা শিথিল করলেও মোট সময় বেড়ে যেতে পারে। অর্থাৎ “ভালো উপকরণ মানেই ভালো ফল” — এই স্বাভাবিক ধারণা greedy নিয়মে খাটে না।
এই অবস্থায় প্রশ্নটি দাঁড়াল: যদি নিয়মটি এমন অপ্রত্যাশিত আচরণ করে, তবে সবচেয়ে খারাপ ক্ষেত্রে এটি কতটা খারাপ? Graham প্রমাণ করেন, যেকোনো তালিকা-ক্রমেই ফল সর্বোত্তমের ২ − ১/m গুণের বেশি নয়, আর এমন উদাহরণ আছে যেখানে ঠিক এতটাই খারাপ হয়। এটি ছিল একটি সম্পূর্ণ নতুন ধরনের বক্তব্য — অ্যালগরিদম সর্বোত্তম দেয় কি না নয়, বরং “কখনোই এর চেয়ে খারাপ নয়”, যা সব ইনপুটে প্রমাণিত।
মজার বিষয়, তখনো NP-completeness-এর ধারণা আসেনি (Cook ১৯৭১, Karp ১৯৭২)। পরে যখন বোঝা গেল scheduling-এর মতো সমস্যাগুলো সম্ভবত দ্রুত নিখুঁতভাবে সমাধান করা যাবে না, Graham-এর ধাঁচটিই হয়ে উঠল আদর্শ: একটি সরল নিয়ম, একটি নিম্নসীমা (মোট কাজ / m আর সবচেয়ে বড় কাজ), আর একটি tight উদাহরণ। ১৯৬৯ সালে তিনি LPT-এর ৪/৩ − ১/(৩m) সীমাও দেন। Graham পরে গণিতে অনেক কাজের জন্য বিখ্যাত হন, কিন্তু scheduling-এর এই বিশ্লেষণকে প্রায়ই approximation অ্যালগরিদমের সবচেয়ে প্রাচীন উদাহরণগুলোর একটি ধরা হয়।
মূল উপলব্ধি: Greedy নিয়মের আচরণ অন্তর্জ্ঞান দিয়ে বিচার করা বিপজ্জনক; worst-case প্রমাণ আর tight উদাহরণ — এই দুটি মিলেই একটি অ্যালগরিদমের সত্যিকারের পরিচয়।
প্র ০২Christofides-এর ১.৫ প্রায় ৪৫ বছর অপরাজিত ছিল, আর ২০২০ সালের উন্নতি মাত্র ১০−৩৬-এর মতো। এত ছোট উন্নতি নিয়ে গবেষকরা এত উচ্ছ্বসিত হলেন কেন?
Nicos Christofides ১৯৭৬ সালে Carnegie Mellon-এ একটি টেকনিক্যাল রিপোর্টে তাঁর অ্যালগরিদম প্রকাশ করেন; প্রায় একই সময়ে সোভিয়েত ইউনিয়নে Anatoliy Serdyukov স্বাধীনভাবে একই ধারণায় পৌঁছান, যদিও রুশ ভাষায় প্রকাশিত সেই কাজ পশ্চিমে অনেক দিন অজানা ছিল। এরপর কয়েক দশক ধরে বহু শক্তিশালী গবেষক ১.৫ ভাঙার চেষ্টা করেছেন। বিশেষ ক্ষেত্রে সাফল্য এসেছে — যেমন “graphic TSP”, যেখানে দূরত্ব একটি unweighted গ্রাফের সবচেয়ে ছোট পথ, সেখানে ২০১০-এর দশকে ১.৪ পর্যন্ত নামা গেছে। কিন্তু সাধারণ metric TSP-তে ১.৫ যেন একটি দেয়াল।
দীর্ঘদিনের একটি অনুমান আছে: Held–Karp LP relaxation-এর integrality gap সম্ভবত ৪/৩ — অর্থাৎ সঠিক অ্যালগরিদম হয়তো ৪/৩-এর কাছাকাছি যেতে পারে। তাহলে ১.৫ কেন টিকে আছে? কারণ Christofides-এর বিশ্লেষণ, matching-এর OPT/2 সীমা আর MST-এর OPT সীমা যোগ করে, খুব “শক্ত” দেখায়, আর নতুন ধারণা ছাড়া কোনো উন্নতি আসছিল না।
Anna Karlin, Nathan Klein আর Shayan Oveis Gharan-এর কাজ (২০২০ সালে প্রকাশিত, পরের বছর একটি শীর্ষ সম্মেলনে সেরা প্রবন্ধের পুরস্কার পায়) MST-এর জায়গায় LP সমাধান থেকে এলোমেলোভাবে বাছাই করা একটি spanning tree নেয়, আর অত্যন্ত সূক্ষ্ম সম্ভাবনা-বিশ্লেষণ দিয়ে দেখায় প্রত্যাশিত খরচ ১.৫ × OPT-এর চেয়ে সামান্য কম। উন্নতির পরিমাণ হাস্যকর রকম ছোট, ব্যবহারিক কোনো মূল্য নেই। কিন্তু গণিতে “ঠিক ১.৫-ই সেরা কি না” আর “১.৫-এর নিচে কিছু সম্ভব” — এই দুটি সম্পূর্ণ আলাদা বিশ্ব। একবার দেয়ালে ফাটল ধরলে নতুন কৌশল দিয়ে আরও উন্নতির আশা জাগে।
মূল উপলব্ধি: তত্ত্বে একটি সীমা ভাঙাই বড় কথা, পরিমাণ নয় — কারণ এটি দেখায় পুরোনো বিশ্লেষণটি মৌলিক সীমা ছিল না, ছিল শুধু আমাদের জানা কৌশলের সীমা।
প্র ০৩Vertex cover-এ সরল matching-অ্যালগরিদম ২ দেয়, আর আজও কেউ ১.৯৯ দিতে পারেননি। আবার একটি সীমার (এখন প্রায় ১.৪১) নিচে যাওয়া NP-hard বলে প্রমাণিত। এই সীমা আর ২-এর মাঝের ফাঁক নিয়ে তত্ত্ব কী বলে?
প্রথমে উপরের দিক: ১৯৭০-এর দশক থেকেই ২-approximation জানা — matching দিয়ে, পরে LP rounding আর primal-dual পদ্ধতিতে (Bar-Yehuda ও Even-সহ অনেকের কাজ), ওজনযুক্ত রূপেও। অনেক চতুর অ্যালগরিদম ২ − Θ(1/log n)-এর মতো সামান্য উন্নতি দেয়, কিন্তু n বড় হলে তা ২-এর দিকেই যায়। কোনো ধ্রুবক ε-এর জন্য ২ − ε কেউ পাননি।
নিচের দিক এসেছে PCP theorem-এর পথ ধরে। Johan Håstad ১৯৯০-এর দশকে ৭/৬ ≈ ১.১৭-এর নিচে vertex cover-কে NP-hard দেখান। ২০০৫ সালে Irit Dinur আর Samuel Safra এটিকে প্রায় ১.৩৬-এ তোলেন — খুব জটিল একটি প্রমাণে। ২০১৮ সালে Khot, Minzer আর Safra-র “2-to-2 games” ফল প্রমাণের পর সীমাটি প্রায় √২ ≈ ১.৪১-এ ওঠে — কিন্তু ২-তে পৌঁছায়নি।
ফাঁকটি বন্ধ করার সবচেয়ে বিখ্যাত চেষ্টা হলো Subhash Khot-এর ২০০২ সালের Unique Games Conjecture (UGC) — একটি নির্দিষ্ট ধরনের constraint-সমস্যায় প্রায়-সব-সন্তুষ্ট আর খুব-কম-সন্তুষ্ট ইনপুট আলাদা করা কঠিন, এই অনুমান। ২০০৮ সালে Khot আর Oded Regev দেখান, UGC সত্য হলে vertex cover-এ যেকোনো ε > 0-এর জন্য ২ − ε NP-hard — অর্থাৎ সেই সরল matching-অ্যালগরিদমই সেরা যা সম্ভব। UGC আজও প্রমাণিত নয়; ওপরের 2-to-2 ফলটিকে অনেকে UGC-র পক্ষে একটি শক্ত ইঙ্গিত হিসেবে দেখেন। Khot ২০১৪ সালে Nevanlinna Prize পান।
মূল উপলব্ধি: কখনো সবচেয়ে সরল অ্যালগরিদমই মৌলিক সীমায় দাঁড়িয়ে থাকে — আর সেটি প্রমাণ করা অ্যালগরিদম লেখার চেয়ে বহু গুণ কঠিন কাজ।
প্র ০৪Set cover-এর greedy ১৯৭৪ সালে বিশ্লেষিত, আর পরে প্রমাণ হলো এর চেয়ে প্রায় ভালো কিছু সম্ভব নয়। এত সরল একটি নিয়ম কেন “সেরা”, আর বাস্তবে set cover কোথায় দেখা যায়?
David Johnson ১৯৭৪ সালে আর László Lovász ১৯৭৫ সালে আলাদাভাবে দেখান greedy-র ratio H(n)-এর মধ্যে; Vašek Chvátal ১৯৭৯ সালে ওজনযুক্ত রূপে একই সীমা দেন। প্রশ্ন ছিল, আরও চতুর অ্যালগরিদম কি ধ্রুবক ratio দিতে পারে? ১৯৯০-এর দশকে PCP-ভিত্তিক কাজে উত্তর আসে “না”: Carsten Lund আর Mihalis Yannakakis প্রথমে log n-এর একটি ভগ্নাংশের সীমা দেন, তারপর Uriel Feige ১৯৯৮ সালে দেখান (1 − ε) ln n-এর চেয়ে ভালো করা যাবে না, একটি সামান্য শক্ত জটিলতা-অনুমানে। ২০১৪ সালে Irit Dinur আর David Steurer সেটিকে সরাসরি “P ≠ NP হলে” রূপে আনেন।
অর্থাৎ greedy-র ln n আর অসম্ভবতার ln n প্রায় মিলে গেছে — এমন নিখুঁত মিল approximation-তত্ত্বে বিরল। কারণটি অনুভব করা যায় pricing যুক্তি থেকে: greedy প্রতিটি ধাপে “নতুন ঢাকার হার” সর্বোচ্চ করে, আর সবচেয়ে খারাপ ক্ষেত্রেও প্রতিটি সর্বোত্তম সেট মাত্র H(d) পরিমাণ দাম বহন করে; কঠিন ইনপুটগুলো ঠিক এই harmonic ধাঁচের ফাঁদ।
বাস্তবে set cover সর্বত্র: মোবাইল টাওয়ার বা Wi-Fi access point বসানো, জরুরি সেবার কেন্দ্র (অ্যাম্বুলেন্স, ফায়ার স্টেশন) এমনভাবে বাছা যাতে প্রতিটি এলাকা নির্দিষ্ট সময়ের মধ্যে পৌঁছায়, সফটওয়্যার টেস্টিং-এ সব কোড-লাইন ছোঁয় এমন সবচেয়ে ছোট test suite, এয়ারলাইনের crew scheduling, আর জীববিজ্ঞানে সব রোগ-চিহ্নক ধরতে সবচেয়ে কম জিন-প্রোব। বাস্তবের অনেক টুল greedy দিয়ে শুরু করে, তারপর integer programming (পর্ব ৫৭-এর branch and bound-এর সাথে) দিয়ে উন্নত করে, আর greedy-র উত্তরই সীমা হিসেবে কাজে লাগে।
মূল উপলব্ধি: অ্যালগরিদমের সরলতা দুর্বলতা নয়; যখন নিম্নসীমা আর উপরের সীমা মিলে যায়, সরল নিয়মটিই প্রমাণসহ সর্বোত্তম পছন্দ।
প্র ০৫বাস্তবের TSP সলভার (যেমন Concorde বা LKH) হাজার হাজার শহরে সর্বোত্তমের এক শতাংশের মধ্যে উত্তর দেয়, অথচ তাত্ত্বিক নিশ্চয়তা ১.৫. তাহলে approximation-এর প্রমাণিত ratio-র বাস্তব মূল্য কী?
প্রথমে মনে রাখুন, approximation ratio একটি worst-case প্রতিশ্রুতি — সবচেয়ে বিরূপ ইনপুটেও এর চেয়ে খারাপ হবে না। বাস্তবের ইনপুট সাধারণত এত বিরূপ নয়। পরীক্ষামূলক গবেষণায় (যেমন David Johnson আর Lyle McGeoch-এর বড় তুলনামূলক কাজে) দেখা গেছে, এলোমেলো ইনপুটে MST-ট্যুর আর Christofides সাধারণত তাদের প্রমাণিত সীমার অনেক নিচে থাকে। আর Lin–Kernighan ধাঁচের local search (LKH তার একটি শক্তিশালী রূপ) প্রায়ই প্রায়-নিখুঁত উত্তর দেয়, যদিও তার কোনো ধ্রুবক নিশ্চয়তা প্রমাণিত নয়। Concorde (Applegate, Bixby, Chvátal ও Cook) আবার নিখুঁত সমাধান খোঁজে — LP relaxation আর cutting plane দিয়ে — আর বিপুল আকারের ইনপুটেও সফল হয়েছে, যদিও সবচেয়ে খারাপ ক্ষেত্রে exponential.
তাহলে প্রমাণিত ratio-র কাজ কী? প্রথমত, নিশ্চয়তা যেখানে দরকার: ক্লাউডে কাজ ভাগ করা বা নেটওয়ার্ক রাউটিং-এর মতো স্বয়ংক্রিয় সিস্টেমে প্রতি সেকেন্ডে লাখো সিদ্ধান্ত হয়, কেউ হাতে দেখে না — সেখানে “কখনো দ্বিগুণের বেশি খারাপ নয়” একটি মূল্যবান চুক্তি। দ্বিতীয়ত, শুরুর বিন্দু হিসেবে: local search ভালো একটি শুরু পেলে দ্রুত ভালো ফল দেয়, আর Christofides বা MST-ট্যুর সেই শুরু হতে পারে। তৃতীয়ত, নিম্নসীমা হিসেবে: approximation-এর প্রমাণ যে LB দেয় (MST, LP), সেটি দিয়ে যেকোনো heuristic-এর উত্তর কত ভালো তা মাপা যায় — “আমার উত্তর ১০৫, নিম্নসীমা ১০০, তাই আমি সর্বোত্তমের ৫%-এর মধ্যে।”
পর্ব ৬৫-এ যে কৌশলগুলো দেখেছেন, সেগুলোর সাথে approximation প্রতিদ্বন্দ্বী নয়, বরং সঙ্গী। অভিজ্ঞ প্রকৌশলী প্রায়ই এভাবে কাজ করেন: দ্রুত একটি প্রমাণিত সমাধান, তারপর heuristic দিয়ে উন্নতি, আর নিম্নসীমা দিয়ে মান যাচাই।
মূল উপলব্ধি: Worst-case ratio গড় মানের ভবিষ্যদ্বাণী নয়, বরং একটি নিরাপত্তা-জাল আর একটি মাপকাঠি — বাস্তবের সেরা সিস্টেম প্রমাণিত নিশ্চয়তা আর অভিজ্ঞতালব্ধ heuristic দুটোই ব্যবহার করে।
অনুশীলন
খাতা-কলম নিন। প্রথম তিনটি হাতে করার, শেষটিতে কোড লিখতে হবে।
-
হাতে trace করুন — vertex cover: ছয়টি মোড় P, Q, R, S, T, U; রাস্তা এই ক্রমে: P–Q, Q–R, R–S, S–T, T–U, U–P, Q–T. Matching-অ্যালগরিদম চালিয়ে কোন রাস্তা match হয় আর cover কী, লিখুন। সর্বোত্তম কত, আর আপনার ratio কত?
P–Q: দুজনই খালি → match, cover {P, Q}. Q–R: Q আছে → বাদ। R–S: খালি → match, {P, Q, R, S}. S–T: S আছে → বাদ। T–U: খালি → match, cover {P, Q, R, S, T, U} — ছয়জন। U–P আর Q–T ঢাকা। Matching-এ ৩টি রাস্তা, তাই OPT ≥ ৩.
গ্রাফটি ছয় vertex-এর একটি cycle (P–Q–R–S–T–U–P) আর একটি অতিরিক্ত Q–T. ছয়-cycle ঢাকতে অন্তত ৩ জন লাগে (প্রতিজন cycle-এর ২টি রাস্তা ঢাকে, রাস্তা ৬টি), আর {P, R, T}: P ঢাকে P–Q, U–P; R ঢাকে Q–R, R–S; T ঢাকে S–T, T–U, Q–T — সব ঢাকা। OPT = ৩, ratio ৬ / ৩ = ২ — এখানে বাউন্ডটি পুরো ছোঁয়া গেছে।
-
হাতে trace করুন — greedy set cover: U = {1, 2, 3, 4, 5, 6}; সেট S1 = {1, 2, 3}, S2 = {3, 4}, S3 = {4, 5, 6}, S4 = {1, 4}, S5 = {2, 5}, S6 = {3, 6}. Greedy চালান (সমান হলে ছোট index), প্রতিটি উপাদানের দাম লিখুন। সর্বোত্তম কত?
শুরুতে নতুন ঢাকার সংখ্যা: S1 ৩, S2 ২, S3 ৩, S4 ২, S5 ২, S6 ২. সমান ৩ — ছোট index, তাই S1; উপাদান ১, ২, ৩-এর দাম ১/৩ করে। বাকি {4, 5, 6}: S2 ১, S3 ৩, S4 ১, S5 ১, S6 ১ → S3; ৪, ৫, ৬-এর দাম ১/৩ করে। সব ঢাকা — greedy ২টি সেট, মোট দাম ৬ × ১/৩ = ২.
একটি সেট দিয়ে হয় না (কোনো সেটে ৬টি উপাদান নেই), তাই OPT = ২, greedy এখানে নিখুঁত। শিক্ষা: H(n)-এর নিশ্চয়তা সবচেয়ে খারাপ ক্ষেত্রের; অনেক ইনপুটে greedy সর্বোত্তমই দেয়। অংশ ৫-এর ফাঁদ তৈরি করতে “একটু বড়” প্রলোভন-সেট লাগে।
-
ভেবে বলুন — list scheduling: m = ২ টি সেলাই লাইন, অর্ডারের সময় (ঘণ্টা) এই ক্রমে: ৩, ৩, ২, ২, ২. (ক) List scheduling-এর makespan কত? (খ) LPT-তে? (গ) সর্বোত্তম কত, আর ফলগুলো কি Graham-এর সীমা মানে?
(ক) ৩ → লাইন ১ (৩), ৩ → লাইন ২ (৩), ২ → লাইন ১ (৫), ২ → লাইন ২ (৫), ২ → লাইন ১ (৭). Makespan ৭. (খ) LPT-তে আগে বড়গুলো — ক্রম একই (৩, ৩, ২, ২, ২), তাই আবার ৭.
(গ) সর্বোত্তম: {৩, ৩} এক লাইনে (৬), {২, ২, ২} অন্যটিতে (৬) — OPT = ৬. নিম্নসীমা মোট ১২ / ২ = ৬, তাই এর চেয়ে ভালো অসম্ভব। Ratio ৭/৬ ≈ ১.১৭. List scheduling-এর সীমা ২ − ১/২ = ১.৫, LPT-র ৪/৩ − ১/৬ = ৭/৬ — LPT ঠিক নিজের সীমায়; এটি m = ২-এর একটি tight উদাহরণ।
-
কোড লিখুন: n টি অর্ডারের সময় আর m দেওয়া আছে। LPT লিখুন: সময় বড় থেকে ছোট সাজান, তারপর min-heap-এ লাইনের বোঝা রেখে প্রতিটি অর্ডার সবচেয়ে কম ব্যস্ত লাইনে দিন; makespan ফেরত দিন। Time complexity কত?
সর্টের পর এটি সাধারণ list scheduling; heap-এর শীর্ষে সবচেয়ে কম বোঝার লাইন:
// LPT: returns the makespan of m machines (4/3 - 1/(3m) approximation) long long lpt(vector<long long> p, int m) { sort(p.rbegin(), p.rend()); // longest first priority_queue<long long, vector<long long>, greater<long long>> load; for (int i = 0; i < m; ++i) load.push(0); long long makespan = 0; for (long long t : p) { long long l = load.top(); load.pop(); // least loaded line load.push(l + t); makespan = max(makespan, l + t); } return makespan; }অনুশীলন ৩-এর ইনপুটে (৩, ৩, ২, ২, ২; m = ২) এটি ৭ দেয়। সর্ট O(n log n), প্রতিটি অর্ডারে heap-এ একটি pop আর push O(log m) — মোট O(n log n + n log m), জায়গা O(m) (সর্টের কপি বাদে)। বোঝা
long long-এ রাখুন — অনেক বড় অর্ডারে int উপচে পড়তে পারে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমMetric TSP-এর MST-দ্বিগুণ (২-approximation) অ্যালগরিদমের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- সব শহরের সম্পূর্ণ গ্রাফে minimum spanning tree বানানো
- শুরুর শহর থেকে tree-তে DFS চালানো (প্রতিটি edge দুবার — নামা আর ফেরা)
- প্রতিটি শহর প্রথমবার দেখার ক্রম (preorder) লিখে রাখা
- আগে দেখা শহর এড়িয়ে সরাসরি পরের নতুন শহরে যাওয়া (শর্টকাট)
- শেষ শহর থেকে শুরুর শহরে ফিরে ট্যুর বন্ধ করা
একটি minimization সমস্যার জন্য একটি 2-approximation অ্যালগরিদম একটি ইনপুটে 30 খরচের উত্তর দিল। সেই ইনপুটের সর্বোত্তম খরচ OPT সম্পর্কে নিশ্চিতভাবে কী বলা যায়?
- OPT = 15
- 15 ≤ OPT ≤ 30
- OPT ≥ 30
- OPT 15-এর কম হতে পারে
একটি গ্রাফে একটি maximal matching-এ 5টি edge আছে। ওই matching-এর সব প্রান্ত নিয়ে বানানো vertex cover-এর আকার আর সর্বোত্তম vertex cover OPT সম্পর্কে কোনটি সত্য?
- Cover-এর আকার 5, OPT = 5
- Cover-এর আকার 10, OPT = 10
- Cover-এর আকার 10, আর 5 ≤ OPT ≤ 10
- Cover-এর আকার 10, আর OPT 5-এর কমও হতে পারে
n উপাদানের universe-এ greedy set cover (প্রতিবার সবচেয়ে বেশি নতুন উপাদান ঢাকে এমন সেট) কোন approximation ratio নিশ্চিত করে?
- H(n) ≈ ln n
- 2
- 1, অর্থাৎ সবসময় সর্বোত্তম
- n
দূরত্ব যেকোনো হতে পারে (ত্রিভুজ অসমতা না মানলেও চলে), এমন সাধারণ TSP-এর approximation সম্পর্কে কোনটি সঠিক?
- MST দ্বিগুণ করলে 2-approximation পাওয়া যায়
- Christofides 1.5-approximation দেয়
- একটি PTAS আছে
- P ≠ NP হলে কোনো ধ্রুবক ρ-approximation নেই
PTAS আর FPTAS-এর মূল পার্থক্য কী?
- PTAS শুধু ε = 1-এর জন্য কাজ করে
- FPTAS-এর সময় n আর 1/ε দুটোরই polynomial; PTAS-এর ক্ষেত্রে 1/ε-এর নির্ভরতা যেকোনো হতে পারে
- FPTAS সবসময় নিখুঁত উত্তর দেয়
- FPTAS অবশ্যই randomized
m = 4 টি মেশিনে Graham-এর list scheduling (প্রতিটি কাজ সবচেয়ে কম ব্যস্ত মেশিনে) makespan-এ কোন নিশ্চয়তা দেয়?
- ≤ (4/3 − 1/12) · OPT
- ≤ 2 · OPT, এর চেয়ে ভালো কিছু বলা যায় না
- ≤ 1.75 · OPT
- সবসময় = OPT
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ρ-approximation = polynomial সময় + প্রতিটি ইনপুটে ALG ≤ ρ · OPT (maximization-এ ALG ≥ OPT/ρ)।
- OPT না জেনে প্রমাণের কৌশল: একটি নিম্নসীমা LB ≤ OPT খুঁজে ALG ≤ ρ · LB দেখানো — matching, MST, মোট কাজ/m, pricing.
- Vertex cover: maximal matching-এর সব প্রান্ত, ≤ 2 · OPT, O(V + E); set cover: greedy, ≤ H(n) · OPT ≈ ln n, আর এর চেয়ে প্রায় ভালো অসম্ভব।
- Metric TSP: MST → DFS → শর্টকাট, ≤ 2 · OPT; Christofides ১.৫; ত্রিভুজ অসমতা না থাকলে কোনো ধ্রুবক ratio নেই।
- List scheduling 2 − 1/m, LPT 4/3 − 1/(3m); knapsack-এর FPTAS O(n³/ε); PCP theorem থেকে inapproximability.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৬৭ · ক্যাপস্টোন — একটি কঠিন সমস্যার সমাধান শুরু থেকে শেষপরবর্তী পর্বকোর্সের অনেক কৌশল — Dijkstra, DSU, MST-এর ধর্ম, stress test — একটি সমস্যায়।
- ডেটা স্ট্রাকচার ও অ্যালগরিদম · কোর্স হোম৭২টি পর্বপুরো কোর্সের রোডম্যাপ — নয়টি মডিউল।
- পর্ব ৬৪ · P বনাম NP — NP-complete আর reductionআগের ভিত্তিNP-hard মানে কী, আর reduction কীভাবে কঠিনতা বহন করে।
- পর্ব ৬৫ · NP-hard সমস্যা বাস্তবে — চেনা আর মোকাবিলাআগের ভিত্তিHeuristic, বিশেষ গঠন আর নিখুঁত সলভার — approximation-এর সঙ্গী কৌশল।
- পর্ব ৪৫ · Minimum spanning tree — Kruskal আর Prim, প্রমাণসহআগের ভিত্তিMST-ট্যুর আর Christofides-এর ভিত্তি।
- C++ প্রোগ্রামিং · STL algorithmপাশের ট্র্যাক
sort,next_permutationআরpriority_queue— brute force আর greedy লেখার তৈরি হাতিয়ার।