问题描述:
用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. 确定状态转移方程
定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。