工作分配問題是一個典型的回溯問題,利用回溯思想能很准確地得到問題的解。我們就針對如下一個案例做一個系統的分析:
問題描述
有 \(n\) 份工作要分配給 \(n\) 個人來完成,每個人完成一份。第 \(i\) 個人完成第 \(k\) 份工作所用的時間為一個正整數 \(t_{ik}\),其中 \(1 \leq i, k \leq n\)。試確定一個分配方案,使得完成這 \(n\) 份工作的時間總和最小。
輸入包含 \(n + 1\) 行。
第 1 行為一個正整數 \(n\)。
第 2 行到第 \(n + 1\) 行中每行都包含 \(n\) 個正整數,形成了一個 \(n \times n\) 的矩陣。在該矩陣中,第 \(i\) 行第 \(k\) 列元素 \(t_{ik}\) 表示第 \(i\) 個人完成第 \(k\) 件工作所要用的時間。
輸出為 1 行,包含一個正整數,表示所有分配方案中最小的時間總和。
限制范圍:
\(1 \leq n \leq 15\)
\(1 \leq t_{ik} \leq 10^4\)
輸入樣例:
5
9 2 9 1 9
1 9 8 9 6
9 9 9 9 1
8 8 1 8 4
9 1 7 8 9
輸出樣例:
5
問題分析
由於每個人都必須分配到工作,在這里可以建一個二維數組 time[i][j]
,用以表示 \(i\) 個人完成 \(j\) 號工作所花費的時間。給定一個循環,從第 1 個人開始循環分配工作,直到所有人都分配到。為第 \(i\) 個人分配工作時,再循環檢查每個工作是否已被分配,沒有則分配給 \(i\) 個人,否則檢查下一個工作。可以用一個一維數組 is_working[j]
來表示第 \(j\) 號工作是否已被分配,未分配則 is_working[j]=0
,否則 is_working[j]=1
。利用回溯思想,在工人循環結束后回到上一工人,取消此次分配的工作,而去分配下一工作直到可以分配為止。這樣,一直回溯到第 1 個工人后,就能得到所有的可行解。在檢查工作分配時,其實就是判斷取得可行解時的二維數組的第一維下標各不相同和第二維下標各不相同。而我們是要得到完成這 \(n\) 份工作的最小時間總和,即可行解中和最小的一個,故需要再定義一個全局變量 cost_time_total_min
表示最終的時間總和,初始 cost_time_total_min
為 time[i][i]
之和,即對角線工作時間相加之和。在所有人分配完工作時,比較 \(count\) 與 cost_time_total_min
的大小,如果 \(count\) 小於 cost_time_total_min
,證明在回溯時找到了一個最優解,此時就把 \(count\) 賦給 cost_time_total_min
。但考慮到算法的復雜度,這里還有一個剪枝優化的工作可以做。就是在每次計算局部費用變量 \(count\) 的值時,如果判斷 \(count\) 已經大於 cost_time_total_min
,就沒必要再往下分配了,因為這時得到的解必然不是最優解。
實現代碼
#include <cstdio>
#define N 16
int is_working[N] = {0};// 某項工作是否被分配
int time[N][N];// 完成某項工作所需的時間
int cost_time_total_min;// 完成 n 份工作的最小時間總和
// i 表示第幾個人,count 表示工作費用總和
inline void work(int i, int count, int n){
// 如果 i 超出了所能分配的最大工作件數,表示分配完成,並且 count 比原來 cost_time_total_min 花費少 則更新 cost_time_total_min 的值
if(i > n && count < cost_time_total_min){
cost_time_total_min = count;
return;
}
// 回溯思想
if(count < cost_time_total_min){
// j 表示第幾件工作
for(int j = 1 ; j <= n; j++){
// 如果工作未被分配 is_working = 0
if(is_working[j] == 0){
// 分配工作 is_working = 1
is_working[j] = 1;
//工作交給第 i + 1 個人
work(i + 1, count + time[i][j], n);
//在一輪迭代完成之后,返回到上一個人,要對此次的工作進行重新分配,將 is_working[j] 重設為 0
is_working[j] = 0;
}
}
}
}
int main(int argc, char const *argv[])
{
setvbuf(stdin, new char[1 << 20], _IOFBF, 1 << 20);
setvbuf(stdout, new char[1 << 20], _IOFBF, 1 << 20);
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
scanf("%d", &time[i][j]);
}
cost_time_total_min += time[i][i];
}
work(1, 0, n);
printf("%d\n", cost_time_total_min);
return 0;
}