問題描述:
用2台處理機A和B處理n個作業。設第i個作業交給機器A處理時需要時間,若由機器B來處理,則需要時間。由於各作業的特點和機器的性能關系,很可能對於某些i,有,而對於某些j,j≠i,有。既不能將一個作業分開由2台機器處理,也沒有一台機器能同時處理2個作業。設計一個動態規划算法,使得這2台機器處理完這n個作業的時間最短(從任何一台機器開工到最后一台機器停工的總時間)。研究一個實例:(a1,a2,a3,a4,a5,a6)=(2,5,7,10,5,2);(b1,b2,b3,b4,b5,b6)=(3,8,4,11,3,4)。
算法設計:
對於給定的2台處理機A和B處理n個作業,找出一個最優調度方案,使2台機器處理完這n個作業的時間最短。
數據輸入:
由文件input.txt提供輸入數據。文件的第1行是1個正整數n, 表示要處理n個作業。接下來的2行中,每行有n個正整數,分別表示處理機A和B處理第i個作業需要的處理時間。
結果輸出:
將計算出的最短處理時間輸出到文件output.txt中。
輸入文件示例 |
輸出文件示例 |
input.txt |
output.txt |
6 2 5 7 10 5 2 3 8 4 11 3 4 |
15
|
問題分析:
對於這個問題,我們可以考慮,當完成第k個任務時,有兩種可能:
一是A處理機完成了第k個任務,那么B處理機完成k個任務的最短時間就與B處理機完成k-1個任務所需的最短時間是相同的
二是B處理機完成了第k個任務,那么B處理機完成k個任務的最短時間就等於B處理機完成k-1個任務的最短時間加上B處理機完成第k個任務所需要的時間
設F[k][x]表示完成第k個任務時A耗費的時間為x的情況下B所花費的最短時間,其中0<=k <= n, 0<=x<= Σai,那么,狀態轉移方程為F[k][x]=minF[k−1][x−ak],F[k−1][x]+bk
處理好特殊情況(如x小於0時)開始填表即可。
最終的結果即是完成n個任務時A和B所需時間的較大值,即max(F[n][x], x).
算法實現:
1 #include<iostream> 2 #include<fstream> 3 #include<string.h> 4 using namespace std; 5 6 int get_result(int a[],int b[],int n) 7 { 8 if(n==1) 9 return min(a[0], b[0]); 10 int sum = 0,result = 10000; 11 for(int i=0;i<n;i++) 12 { 13 sum+=a[i]; 14 } 15 int f[n][sum+1]; 16 //初始化f(B處理用的時間)的各個元素為0 17 memset(f, 0, sizeof(f)); 18 19 //初始化完成第一個任務時的情況 20 for(int x=0;x<a[0];x++) 21 { 22 f[0][x]=b[0]; 23 } 24 f[0][a[0]]=0; 25 26 //動態規划過程 27 sum=a[0]; 28 for(int k=1;k<n;k++) 29 { 30 sum+=a[k]; 31 for(int x=0;x<=sum;x++) 32 { 33 if(x<a[k]) 34 { 35 f[k][x]=f[k-1][x]+b[k]; 36 } 37 else 38 { 39 f[k][x]=min(f[k-1][x-a[k]],f[k-1][x]+b[k]); 40 } 41 if(k==n-1) 42 { 43 int val = max(x,f[k][x]); 44 if(val<result) 45 result = val; 46 } 47 } 48 } 49 return result; 50 } 51 52 int main() 53 { 54 int n; 55 ifstream ifs;//創建文件流 56 ofstream ofs; 57 ifs.open("input.txt"); 58 ofs.open("output.txt"); 59 if(!ifs.is_open()||!ofs.is_open()) 60 { 61 cout<<"open failed!"<<endl; 62 return 0; 63 } 64 ifs>>n; 65 int a[n+1],b[n+1]; 66 for(int i=0;i<n;i++) 67 { 68 ifs>>a[i]; 69 } 70 for(int i=0;i<n;i++) 71 { 72 ifs>>b[i]; 73 } 74 int result=get_result(a,b,n); 75 cout<<result<<endl; 76 ofs<<result; 77 ifs.close(); 78 ofs.close(); 79 return 0; 80 81 }
運行結果:
算法分析:
在get_result函數中,有兩個嵌套for循環,時間復雜度為O(n*sum),所以時間復雜度為O(n2)。
經驗歸納:
動態規划解題的一般思路:
1. 將原問題分解為子問題
把原問題分解為若干個子問題,子問題和原問題形式相同或類似,只不過規模變小了。子問題都解決,原問題即解決。
子問題的解一旦求出就會被保存,所以每個子問題只需求解一次。
2.確定狀態
在用動態規划解題時,我們往往將和子問題相關的各個變量的一組取值,稱之為一個“狀 態”。一個“狀態”對應於一個或多個子問題, 所謂某個“狀態”下的“值”,就是這個“狀 態”所對應的子問題的解。
所有“狀態”的集合,構成問題的“狀態空間”。“狀態空間”的大小,與用動態規划解決問題的時間復雜度直接相關。 在數字三角形的例子里,一共有N×(N+1)/2個數字,所以這個問題的狀態空間里一共就有N×(N+1)/2個狀態。
整個問題的時間復雜度是狀態數目乘以計算每個狀態所需時間。在數字三角形里每個“狀態”只需要經過一次,且在每個狀態上作計算所花的時間都是和N無關的常數。
3.確定一些初始狀態(邊界狀態)的值
4. 確定狀態轉移方程
定義出什么是“狀態”,以及在該“狀態”下的“值”后,就要找出不同的狀態之間如何遷移――即如何從一個或多個“值”已知的 “狀態”,求出另一個“狀態”的“值”(遞推型)。狀態的遷移可以用遞推公式表示,此遞推公式也可被稱作“狀態轉移方程”。