মিন-কস্ট-ফ্লো ব্যবহার করে অ্যাসাইনমেন্ট প্রবলেম সমাধান#
অ্যাসাইনমেন্ট প্রবলেমের দুটো সমতুল্য স্টেটমেন্ট আছে:
- একটা বর্গ ম্যাট্রিক্স $A[1..N, 1..N]$ দেওয়া আছে, এতে $N$ টা উপাদান বাছাই করতে হবে যেন প্রতিটা সারি ও কলামে ঠিক একটা উপাদান সিলেক্টেড হয় এবং এই উপাদানগুলোর মানের যোগফল সর্বনিম্ন হয়।
- $N$ টা অর্ডার এবং $N$ টা মেশিন আছে। প্রতিটা অর্ডারের জন্য প্রতিটা মেশিনে তৈরির খরচ জানা আছে। প্রতিটা মেশিনে শুধু একটা অর্ডার করা যায়। সব অর্ডার মেশিনগুলোতে এমনভাবে অ্যাসাইন করতে হবে যেন মোট খরচ সর্বনিম্ন হয়।
এখানে আমরা মিনিমাম কস্ট ফ্লো (মিন-কস্ট-ফ্লো) বের করার অ্যালগরিদমের উপর ভিত্তি করে সমস্যার সমাধান দেখব, যেটা $\mathcal{O}(N^3)$ সময়ে অ্যাসাইনমেন্ট প্রবলেম সমাধান করে।
বর্ণনা#
চলো একটা বাইপার্টাইট (bipartite) নেটওয়ার্ক বানাই: একটা সোর্স $S$, একটা সিংক $T$, প্রথম অংশে $N$ টা ভার্টেক্স (ম্যাট্রিক্সের সারি বা অর্ডারের সাথে সঙ্গতিপূর্ণ), দ্বিতীয় অংশেও $N$ টা ভার্টেক্স (ম্যাট্রিক্সের কলাম বা মেশিনের সাথে সঙ্গতিপূর্ণ)। প্রথম সেটের প্রতিটা ভার্টেক্স $i$ এবং দ্বিতীয় সেটের প্রতিটা ভার্টেক্স $j$-এর মধ্যে ব্যান্ডউইথ ১ এবং কস্ট $A_{ij}$ সহ একটা এজ আঁকি। সোর্স $S$ থেকে প্রথম সেটের সব ভার্টেক্স $i$-তে ব্যান্ডউইথ ১ এবং কস্ট ০ সহ এজ আঁকি। দ্বিতীয় সেটের প্রতিটা ভার্টেক্স $j$ থেকে সিংক $T$-তে ব্যান্ডউইথ ১ এবং কস্ট ০ সহ একটা এজ আঁকি।
এই নেটওয়ার্কে মিনিমাম কস্টের ম্যাক্সিমাম ফ্লো বের করি। স্পষ্টতই, ফ্লো-এর মান $N$ হবে। এছাড়াও, প্রথম সেগমেন্টের প্রতিটা ভার্টেক্স $i$-এর জন্য দ্বিতীয় সেগমেন্টের ঠিক একটা ভার্টেক্স $j$ আছে, যেখানে ফ্লো $F_{ij}$ = ১। শেষমেশ, এটা প্রথম সেগমেন্ট ও দ্বিতীয় অংশের ভার্টেক্সগুলোর মধ্যে একটা এক-এক সঙ্গতি, যেটা সমস্যার সমাধান (যেহেতু প্রাপ্ত ফ্লো-এর কস্ট সর্বনিম্ন, তাই সিলেক্টেড এজগুলোর কস্টের যোগফল সম্ভাব্য সর্বনিম্ন হবে, যেটা অপটিমালিটির শর্ত)।
এই সমাধানের কমপ্লেক্সিটি নির্ভর করে মিনিমাম কস্টের ম্যাক্সিমাম ফ্লো বের করতে কোন অ্যালগরিদম ব্যবহার করা হচ্ছে তার উপর। ডায়াক্সট্রা ব্যবহার করলে কমপ্লেক্সিটি হবে $\mathcal{O}(N^3)$ আর বেলম্যান-ফোর্ড ব্যবহার করলে $\mathcal{O}(N^4)$। কারণটা হলো ফ্লো-এর আকার $O(N)$ এবং ডায়াক্সট্রা অ্যালগরিদমের প্রতিটা ইটারেশন $O(N^2)$ সময়ে করা যায়, যেখানে বেলম্যান-ফোর্ডের জন্য এটা $O(N^3)$।
ইমপ্লিমেন্টেশন#
এখানে দেওয়া ইমপ্লিমেন্টেশনটা বেশ লম্বা, এটা সম্ভবত অনেকটাই ছোট করা যায়। এটা শর্টেস্ট পাথ বের করার জন্য SPFA অ্যালগরিদম ব্যবহার করে।
const int INF = 1000 * 1000 * 1000;
vector<int> assignment(vector<vector<int>> a) {
int n = a.size();
int m = n * 2 + 2;
vector<vector<int>> f(m, vector<int>(m));
int s = m - 2, t = m - 1;
int cost = 0;
while (true) {
vector<int> dist(m, INF);
vector<int> p(m);
vector<bool> inq(m, false);
queue<int> q;
dist[s] = 0;
p[s] = -1;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
inq[v] = false;
if (v == s) {
for (int i = 0; i < n; ++i) {
if (f[s][i] == 0) {
dist[i] = 0;
p[i] = s;
inq[i] = true;
q.push(i);
}
}
} else {
if (v < n) {
for (int j = n; j < n + n; ++j) {
if (f[v][j] < 1 && dist[j] > dist[v] + a[v][j - n]) {
dist[j] = dist[v] + a[v][j - n];
p[j] = v;
if (!inq[j]) {
q.push(j);
inq[j] = true;
}
}
}
} else {
for (int j = 0; j < n; ++j) {
if (f[v][j] < 0 && dist[j] > dist[v] - a[j][v - n]) {
dist[j] = dist[v] - a[j][v - n];
p[j] = v;
if (!inq[j]) {
q.push(j);
inq[j] = true;
}
}
}
}
}
}
int curcost = INF;
for (int i = n; i < n + n; ++i) {
if (f[i][t] == 0 && dist[i] < curcost) {
curcost = dist[i];
p[t] = i;
}
}
if (curcost == INF)
break;
cost += curcost;
for (int cur = t; cur != -1; cur = p[cur]) {
int prev = p[cur];
if (prev != -1)
f[cur][prev] = -(f[prev][cur] = 1);
}
}
vector<int> answer(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (f[i][j + n] == 1)
answer[i] = j;
}
}
return answer;
}