第1题 带时限的作业排序问题
问题描述:
设有一个单机系统、无其它资源限制且每个作业运行相等时间,不妨假定每个作业运行 1 个单位时间。现有 n 个作业,每个作业都有一个截止期限di>0,di 为整数。如果作业能够在截止期限之内完成,可获得 pi>0 的收益。问题要求得到一种作业调度方案,该方案给出作业的一个子集和该作业子集的一种排列,使得若按照这种排列次序调度作业运行,该子集中的每个作业都能如期完成,并且能够获得最大收益。
输入:
第一行输入 n 的值,以下 n 行输入作业号 i,收益 pi,截止期限 di。
输出:
n 个作业的一个最优子集。
示例 :
输入:
4
1 100 2
2 10 1
3 15 2
4 27 1
1 4
输出: 0 0 1 1
第2题 带时限的作业排序问题II
问题描述:
带时限的作业排序问题可描述为:设有 n 个作业和一台处理机,每个作业所需的处理时间、要求的时限和收益可用三元组(pi, di, ti),0<=i<n 表示,其中,作业 i 的所需时间为 ti,如果作业 i 能够在时限 di 内完成,将可收益 pi,求使得总收益最大的作业子集 J。
输入:
第一行输入 n 的值;第二行输入 pi;第三行输入 di;第四行输入 ti (i=0,…,n-1 且作业已经按时限非减次序排列)。
输出:
xi(用固定长度 n-元组 xi 表示,xi=0 或 1,i=0,…,n-1)。
示例 :
输入:
4
5 3 6 10
1 1 2 3
1 1 1 2
输出: 0 0 1 1
第1题
思路分析
这题的思路比较明显,就是每次都选取作业收益最大的,并把它放在允许的最大的截止时间内完成,符合贪心算法的基本思想。将输入的每个作业按收益从大到小进行排序,用一个数组vis表示某时刻是否已经有作业正在运行,vis[i]=1,表示时间i被占用 。然后从第1个作业开始往后搜索,将该作业安排到vis[d]时间运行,如果d已经有安排,将从d-1开始往前搜索,直到找到一个未被占用的时间点。如果找不到空时间点,则跳过此作业,继续向后搜索直到所有作业都搜索完。
1 #include <iostream> 2 #include <stdio.h> 3 #include <algorithm> 4 #include <string.h> 5 6 using namespace std; 7 8 #define MAXN 1100 9 10 struct Node 11 { 12 int id; //编号 13 int p; //收益 14 int d; //截止期限 15 }node[MAXN]; 16 17 int n; 18 int vis[MAXN]; //第i个单位时间是否有作业在运行 19 vector<int> res; //保存结果 20 21 bool cmp(Node a, Node b) 22 { 23 return a.p > b.p; 24 } 25 26 int main() 27 { 28 scanf("%d", &n); 29 for(int i = 0; i < n; ++i) 30 scanf("%d%d%d", &node[i].id, &node[i].p, &node[i].d); 31 //按照收益从大到小排序 32 sort(node, node+n, cmp); 33 memset(vis, 0, sizeof(vis)); 34 35 for(int i = 0; i < n; ++i) 36 { 37 // 如果到期时间没有被占用,则直接占用到期时间; 38 // 否则,继续往前找一个未被占用的时间 39 if(!vis[node[i].d]) 40 { 41 vis[node[i].d] = 1; 42 res.push_back(node[i].id); 43 } 44 else 45 { 46 for(int j = node[i].d-1; j >= 1; --j) 47 { 48 if(!vis[j]) 49 { 50 res.push_back(node[i].id); 51 vis[j] = 1; 52 break; 53 } 54 } 55 } 56 } 57 58 for(int i = 0; i < res.size(); ++i) 59 printf("%d ", res[i]); 60 61 return 0; 62 }
复杂度分析
从代码来看,sort函数排序是O(nlogn)的时间复杂度,最差情况下,d >= n,且每组数据的截止期限都是d时,每次都要往前找一个空的时间点,此时需要找1+2+3+…+n次,所以耗费的时间为O(n2)。由于各个子块不是嵌套的而是顺序的,所以时间复杂度取最高的那个,所以整体来说时间复杂度是O(n2)。
对于空间复杂度,vis[]数组的大小由最大截止日期d决定。所以空间复杂度是O(d)。
第2题
这题和前面这题很相似,但其实解法有很大的差异。这题如果还是按前面那题的贪心思想去解题,是做不出来的,很难找到贪心算法中的最优度量标准。这题是一道比较难解决的问题,我在网上也没找到解决方法。后来询问一些算法比较厉害的同学之后,发现这题其实就是个0-1背包问题。
解题策略就是先选出最大的截止时间dmax,然后从1到dmax,进行0-1背包算法的操作。通过0-1背包的思路就可以找到最优解,这题相较于经典的0-1背包问题还是多了一些内容,那就是要记录背包的路径,最后输出作业的子集的过程其实就是打印背包路径的过程。
1 #include <iostream> 2 #include <stdio.h> 3 4 using namespace std; 5 6 int n; 7 int p[110], d[110], t[110]; 8 int memo[1110]; //memo[i]表示截止期限为i时的最大收益 9 int path[1110][1110]; //记录背包路径 10 11 //递归打印背包路径 12 void printPath(int i, int j) 13 { 14 if(i < 0 || j < 0) 15 return; 16 17 if(path[i][j] == 1) 18 printPath(i-1, j-t[i]); 19 else 20 printPath(i-1, j); 21 22 printf("%d ", path[i][j]); 23 24 } 25 26 int main() 27 { 28 scanf("%d", &n); 29 int dmax = -1; //最大截止时间 30 for(int i = 0; i < n; ++i) 31 scanf("%d", &p[i]); 32 for(int i = 0; i < n; ++i) 33 scanf("%d", &d[i]); 34 35 //题中已经说明输入按时限非减次序排列,所以d[n-1]就是最大的截止期限 36 dmax = d[n-1]; 37 38 for(int i = 0; i < n; ++i) 39 scanf("%d", &t[i]); 40 41 for(int i = 0; i <= dmax; ++i) 42 { 43 if(i >= t[0]) 44 { 45 memo[i] = p[0]; 46 path[0][i] = 1; //记录路径 47 } 48 else 49 memo[i] = 0; 50 } 51 52 for(int i = 1; i < n; ++i) 53 { 54 for(int j = dmax; j >= t[i]; --j) 55 { 56 if(memo[j] < p[i] + memo[j-t[i]]) 57 { //物品装入背包,记录路径 58 memo[j] = p[i] + memo[j-t[i]]; 59 path[i][j] = 1; //记录路径 60 } 61 } 62 } 63 64 //递归打印背包路径 65 printPath(n-1, dmax); 66 67 return 0; 68 }
复杂度分析
从代码来看,这个程序运行的时间主要消耗在第一次的双重循环中,外层循环的次数为n,内层循环次数为dmax,所以这个算法的平均时间复杂度为O(n*dmax)。