一、題目:工作分配問題
二、問題描述
設有n件工作分配給n個人。將工作i分配給第j個人所需的費用為cij 。 設計一個算法,對於給定的工作費用,為每一個人都分配1 件不同的工作,並使總費用達到最小。
在這里給出一組輸入。例如:
3
10 2 3
2 3 4
3 4 5
在這里給出相應的輸出。例如:
9
三、算法描述:
解空間:空間樹為n棵多叉樹,比如有n人對n建工作,那么就會有 n課樹 每個子節點有n個叉口,這么多的叉口得靠一個記錄數組b來進行約束剪去。該樹的根結點是第一個人選某一件工作(即第一棵樹的根結點是第一件工作,第二課樹的根結點是選擇了第二件工作.......第n課的根結點是第一個人選擇了第n個工作),依次類推下去第二層就是第二個人選擇某件工作,在此約束函數if(!b[i])就可以用來判斷該工作是否已經被選擇,比如第一棵樹第一個人已經選擇第一件工作並且作為了根結點了,那么第二人就不能再選擇第一件工作了,那么第二層第一件工作的那個叉口接下去的所有節點構成的樹枝也就會被約束函數剪除掉。 在構建數的時候在腦子中想固然是要將整課樹構建出來,才有利於我們的遍歷,然后我們只需要在遍歷的時候使用一些限制的函數去吧樹枝剪除掉就行了,就不會出現很大的遍歷次數。
所以在代碼中我們進行了n循環的遍歷,其實就是相當於對n課樹的遍歷,不斷找出最優值,在此運用限界函數sum<minn 即當遍歷到該點的時候,如果該點當前的sum值大於當前最優值minn的話就不再進行遍歷,以為再遍歷下去的話那個樹枝產生的解也不可能比當前最優解小,那就沒有意義,所以也就可以將該樹枝剪除了,也就是將該點視為死結點,然后回溯上去。找到這棵樹的最優值之后,進入第二個循環遍歷第二棵空間樹(也就是第一個人選擇了第二件工作的那棵樹),不斷遍歷找到最優值然后賦值給minn。
剪枝函數: sum<minn 為限界函數,當到達該節點時,如果所需耗費的總費用sum小於當 前最優解費用minn時就繼續深度優先遍歷這個節點以下的部分,否則剪枝。!b[r]為約束函數,當該節點已經在問題的解中的時候就不再訪問它。
源代碼:
1 #include<iostream> 2 using namespace std; 3 int a[100][100],sum=0,minn=2147483647,i,j,n; 4 int b[100]; 5 void dfs(int dep) 6 { 8 int r; 9 for (r=1;r<=n;++r)//dep表示第幾個人,r表示工作 10 if (!b[r]) 11 { 12 b[r]=1; 13 sum+=a[dep][r];//a[dep][r]表示第dep個人做第r個工作的費用 14 if (dep==n&&sum<minn) 15 { 16 minn=sum; 17 } 18 if(dep!=n) 19 { 20 if (sum<minn)//剪枝 21 dfs(dep+1); 22 } 23 sum-=a[dep][r];//回溯一步 24 b[r]=0; 25 } 26 } 27 int main() 28 { 29 cin>>n; 30 for (i=1;i<=n;++i) 31 for (j=1;j<=n;++j) 32 cin>>a[i][j]; 33 dfs(1); 34 cout<<minn<<endl; 35 return 0; 36 }
四、心得體會
在算法的醞釀過程中,最好是把空間樹直接畫出來,會比較清晰一些,分析問題能夠更加簡便。而且不要直接把畫出來的空間樹就直接用函數剪枝,吧完整的樹畫出來,然后分析一下樣例是怎么走的流程,那樣子將會更加有利於你解題。