每日一题(20)——高效安排见面会
2012-11-30 18:43
204 查看
一、题目
在校园招聘的季节里,为了能让学生们更好地了解微软亚洲研究院各研究组的情况,HR部门计划为每一个研究组举办一次见面会,让各 个研究组的员工能跟学生相互了解和交流。已知有n位学生,他们分别对m个研究组中的若干个感兴趣。为了满足所有学生的要求,HR希望每 个学生都能参加自己感兴趣的所有见面会。如果每个见面会的时间为t,那么,如何安排才能够使得所有见面会的总时间最短? 最简单的办法,就是把m个研究组的见面会时间依次排开,那我们就要用m * t的总时间,我们有10多个研究小组,时间会拖得很长,能否进一步提高效率?
二、分析
此题的官方解法是将问题转化为一个已知的图的问题:即图的最少着色问题。 但有两点感觉不太好:
一是这个问题的转换感觉角度有些大,不够平滑。 如果此前没听过图的最少着色问题,那么是怎么也不可能想到这儿的。
二是关于此题的分析与解法讲解非常笼统,尤其是解法,寥寥数语就完了 - 我觉得是没有把问题讲清楚的 - 虽然读者自己可以阅读“ 图的最少着色问题”来获得更多的了解,但既然此题作为单独的一题存在,我觉得还是讲清楚点好。
另外,自己思考了一下,觉的此题不转化成图的最少着色问题,通过简单的递归,应该也是可以实现的。
思路分析:(递归&合并的思想)
已知有n位学生,m个见面会,且每个学生可以选择参加任意多个见面会。我们的目的是在没有冲突的情况下把某几个见面会的时间重叠起来,同时开。何为冲突,比如学生甲参加了见面会A与B,那么A与B就是冲突的,因为如果同时开的话,学生甲必然要放弃一个。
那么,我们可以按以下方式,逐个考虑见面会:
对于见面会A,因为其前面没有见面会,略过;
对于见面会B,考虑它和其前面的见面会A是否冲突,如果不冲突,就将B和A合并,继续考虑C;而另外还有一个分支是不管是否冲突,此时不做合并,直接考虑C;
对于见面会C,考虑它和其前面的见面会A,B是否冲突,第一个分支是如果与A不冲突, 就将C和A合并,继续考虑D;第二个分支是如果与B不冲突, 就将C和B合并,继续考虑D;而第三格分支还是不做任何合并,直接考虑D
按此规则继续对下一个见面会做考察
当对最后一个招聘会做完考察后,记下其时间,程序然后递归回溯,会由其他分支继续考察最后一个招聘会,比较并记录最短的那个,这样,当所有的分支都考察过后,记录的那个最短时间就是全局最短的时间了。
代码
输入数据可以用一个二维数组input[m]
来表示,行表示见面会,列表示学生,数组元素表示某学生是否参加该见面会。
递归过程用当前考虑的见面会控制,当最后一个见面会考虑完之后,就得到一个候选解,程序然后回溯,从另一分支再次进入,考虑最后一个见面会,与之前的候选解比较,保存较优的那个:候选解包括见面会时间与当前的具体安排
复杂度分析:
可以注意到,我们需要逐个考虑见面会,一共是m个;而在考虑第i个见面会时,我们最多可能会产生出i个分支,不难看出,总问题的规模为m!;
而产生每个分支时需要做的计算是O(n)的冲突检测与合并,于是,整个的算法复杂度为:O(m! * n)
三、第二种解决方法
寻找活动集合S的最大相互兼容集合的方法[2],在《算法导论》贪心算法一章中有详细的讲解。解决此问题的具体步骤可总结为:
1)把面试集合S中的元素按其结束时间的递增顺序排列;
2)求其最大相互兼容面试集合M,并从原始集合S中删除之;
3)求集合S-M的最大相互兼容面试集合Q,并从集合S-M中删除之。重复此过程直到原始面试集合S中为空集为止;
4)统计求出的最大相互兼容面试集合的个数,即是面试集合S所需的最少面试点个数。
以上面的面试集合S为例,上述步骤用图像表示如下:每个图中用矩形框起来的面试为所属集合的最大相互兼容集合,一共有5个,即至少需要5个面试点。
四、扩展问题一:
研究院一天有N场面试召开(第i场面试:开始时间B[i],结束时间E[i]),每个应聘者只能参加一场面试,为了有一个好的面试环境,N场面试被安排在若干地点。不同的面试在同一时间不能被安排在同一地点。问至少需要多少个面试地点?
分析:
实际上转化一下,就是求区间的最大重叠次数:即每个时间点的重叠次数,最后找到最大重叠次数即为所求。
书上P60的算法,比较巧妙,但要注意的是:排序时要用到双关键字比较,当两个值相等时,属于时间段开始的一定要排在属于时间段结束的后面,只有这样才能保证结果的正确性。(假设[3, 4)和[4, 5)能在同一个地方举行。书上区间段都是用闭区间,本文采用前闭后开。)
考虑到面试安排的时间一般安排在某个整点、半点或者某刻,可以采用计数的方法,如果都安排在整点,每处理一个区间[a, b),就对[a, b)间的所有整数计数一次。最后从计数结果中找出最大值即可。时间复杂度为O(n)(准确的讲,应该是O(k*n),k为区间的最大间隔,k<=24)。如果面试安排时间在某个半点、刻,可以对原来的时间乘以一个整数(比如2或4,这实际上就是桶排序设置桶间隔为0.5或0.25)。
在校园招聘的季节里,为了能让学生们更好地了解微软亚洲研究院各研究组的情况,HR部门计划为每一个研究组举办一次见面会,让各 个研究组的员工能跟学生相互了解和交流。已知有n位学生,他们分别对m个研究组中的若干个感兴趣。为了满足所有学生的要求,HR希望每 个学生都能参加自己感兴趣的所有见面会。如果每个见面会的时间为t,那么,如何安排才能够使得所有见面会的总时间最短? 最简单的办法,就是把m个研究组的见面会时间依次排开,那我们就要用m * t的总时间,我们有10多个研究小组,时间会拖得很长,能否进一步提高效率?
二、分析
此题的官方解法是将问题转化为一个已知的图的问题:即图的最少着色问题。 但有两点感觉不太好:
一是这个问题的转换感觉角度有些大,不够平滑。 如果此前没听过图的最少着色问题,那么是怎么也不可能想到这儿的。
二是关于此题的分析与解法讲解非常笼统,尤其是解法,寥寥数语就完了 - 我觉得是没有把问题讲清楚的 - 虽然读者自己可以阅读“ 图的最少着色问题”来获得更多的了解,但既然此题作为单独的一题存在,我觉得还是讲清楚点好。
另外,自己思考了一下,觉的此题不转化成图的最少着色问题,通过简单的递归,应该也是可以实现的。
思路分析:(递归&合并的思想)
已知有n位学生,m个见面会,且每个学生可以选择参加任意多个见面会。我们的目的是在没有冲突的情况下把某几个见面会的时间重叠起来,同时开。何为冲突,比如学生甲参加了见面会A与B,那么A与B就是冲突的,因为如果同时开的话,学生甲必然要放弃一个。
那么,我们可以按以下方式,逐个考虑见面会:
对于见面会A,因为其前面没有见面会,略过;
对于见面会B,考虑它和其前面的见面会A是否冲突,如果不冲突,就将B和A合并,继续考虑C;而另外还有一个分支是不管是否冲突,此时不做合并,直接考虑C;
对于见面会C,考虑它和其前面的见面会A,B是否冲突,第一个分支是如果与A不冲突, 就将C和A合并,继续考虑D;第二个分支是如果与B不冲突, 就将C和B合并,继续考虑D;而第三格分支还是不做任何合并,直接考虑D
按此规则继续对下一个见面会做考察
当对最后一个招聘会做完考察后,记下其时间,程序然后递归回溯,会由其他分支继续考察最后一个招聘会,比较并记录最短的那个,这样,当所有的分支都考察过后,记录的那个最短时间就是全局最短的时间了。
代码
输入数据可以用一个二维数组input[m]
来表示,行表示见面会,列表示学生,数组元素表示某学生是否参加该见面会。
递归过程用当前考虑的见面会控制,当最后一个见面会考虑完之后,就得到一个候选解,程序然后回溯,从另一分支再次进入,考虑最后一个见面会,与之前的候选解比较,保存较优的那个:候选解包括见面会时间与当前的具体安排
复杂度分析:
可以注意到,我们需要逐个考虑见面会,一共是m个;而在考虑第i个见面会时,我们最多可能会产生出i个分支,不难看出,总问题的规模为m!;
而产生每个分支时需要做的计算是O(n)的冲突检测与合并,于是,整个的算法复杂度为:O(m! * n)
#include <iostream> #include <list> #include <vector> using namespace std; // If 2 recruiting meetings are conflicting bool IsConflict(const vector<bool>& v1, const vector<bool>& v2) { for(int i = 0; i < v1.size(); ++i) { if(v1[i] && v2[i]) return true; } return false; } // merge 2 recruiting meetings: v2 will be held at the same time of v1 void Merge(vector<bool>& v1, const vector<bool>& v2) { for(int i = 0; i < v1.size(); ++i) { v1[i] = v1[i] || v2[i]; } } // input: input[m] , 2d array to represents students' selection of meetings. // row stands for meetings, column stands for students, // and array value stands for if a student select a meeting // check: The meeting to check // curTime: Time required so far // curArrangement: record the information of which meeting is merged with another //meeting // best[output]:The best time // bestArrangement[output] : The best arragement void ArrangeRecruitings(vector<vector<bool> >& input, int check, int& curTime, vector<list<int> >& curArrangement, int& bestTime, vector<list<int> >& bestArrangement) { int m = input.size(); // base cases if(check >= m) { // Save the best one if(curTime < bestTime) { bestTime = curTime; bestArrangement = curArrangement; } return; } else { // recursive cases for(int i = 0; i <= check; ++i) // check: The meeting to check { if(curArrangement[i].empty()) continue; // if already merged with other ones, just skip it if (!IsConflict(input[i], input[check])) { // update the status vector<bool> bkI = input[i]; Merge(input[i], input[check]); curArrangement[check].pop_back(); curArrangement[i].push_back(check); // Consider check one after merge ArrangeRecruitings(input, check+1, curTime, curArrangement, bestTime, bestArrangement); // restore the status;bestarranyment has been saved curArrangement[i].pop_back(); curArrangement[check].push_back(check); input[i] = bkI; } } // Consider check one without any merge ArrangeRecruitings(input, check+1, ++curTime, curArrangement, bestTime, bestArrangement); } } int main() { // 1. Initializing int m = 3; //meetings int n = 4; //students vector<vector<bool> > input(m, vector<bool>(n, false)); input[0][0] = true; //input[0][1] = true; input[1][1] = true; input[1][2] = true; input[2][2] = true; input[2][3] = true; input[0][3] = true; int curTime = 1; int bestTime = m+1; vector<list<int> > curArrangement(m); vector<list<int> > bestArrangement(m); for(int i = 0; i < m; ++i) curArrangement[i].push_back(i); // 2. Solve ArrangeRecruitings(input, 1, curTime, curArrangement, bestTime, bestArrangement); // 3. Output the result cout << "Totoal Time: " << bestTime << endl; for(int i = 0; i < m; ++i) { cout << i << "): "; if(bestArrangement[i].empty()) { cout << "none" << endl; continue; } for(list<int>::const_iterator it = bestArrangement[i].begin(); it != bestArrangement[i].end(); ++it) cout << *it << "-"; cout << endl; } }
三、第二种解决方法
寻找活动集合S的最大相互兼容集合的方法[2],在《算法导论》贪心算法一章中有详细的讲解。解决此问题的具体步骤可总结为:
1)把面试集合S中的元素按其结束时间的递增顺序排列;
2)求其最大相互兼容面试集合M,并从原始集合S中删除之;
3)求集合S-M的最大相互兼容面试集合Q,并从集合S-M中删除之。重复此过程直到原始面试集合S中为空集为止;
4)统计求出的最大相互兼容面试集合的个数,即是面试集合S所需的最少面试点个数。
以上面的面试集合S为例,上述步骤用图像表示如下:每个图中用矩形框起来的面试为所属集合的最大相互兼容集合,一共有5个,即至少需要5个面试点。
四、扩展问题一:
研究院一天有N场面试召开(第i场面试:开始时间B[i],结束时间E[i]),每个应聘者只能参加一场面试,为了有一个好的面试环境,N场面试被安排在若干地点。不同的面试在同一时间不能被安排在同一地点。问至少需要多少个面试地点?
分析:
实际上转化一下,就是求区间的最大重叠次数:即每个时间点的重叠次数,最后找到最大重叠次数即为所求。
书上P60的算法,比较巧妙,但要注意的是:排序时要用到双关键字比较,当两个值相等时,属于时间段开始的一定要排在属于时间段结束的后面,只有这样才能保证结果的正确性。(假设[3, 4)和[4, 5)能在同一个地方举行。书上区间段都是用闭区间,本文采用前闭后开。)
考虑到面试安排的时间一般安排在某个整点、半点或者某刻,可以采用计数的方法,如果都安排在整点,每处理一个区间[a, b),就对[a, b)间的所有整数计数一次。最后从计数结果中找出最大值即可。时间复杂度为O(n)(准确的讲,应该是O(k*n),k为区间的最大间隔,k<=24)。如果面试安排时间在某个半点、刻,可以对原来的时间乘以一个整数(比如2或4,这实际上就是桶排序设置桶间隔为0.5或0.25)。
//arr[][0]为面试开始时间,arr[][1]为面试结束时间 int max_places (int arr[][2], size_t sz) { if (arr==NULL || sz<1) return 0; const size_t MAX_HOURS=24; int count[MAX_HOURS]={0}; int max=0, j=0; size_t i=0; for (i=0; i<sz; ++i) for (j=arr[i][0]; j<arr[i][1]; ++j) ++count[j];//前闭后开区间 for (i=0; i<MAX_HOURS; ++i) if (count[i]>max) max=count[i]; return max; }
相关文章推荐
- 每日一题(20)——高效安排见面会
- 高效的安排见面会
- 高效地安排见面会扩展问题
- 编程之美之高效安排见面会
- Excel VBA高效办公应用-第九章-VBA文秘办公技巧-Part1 (每日行程安排提醒)
- 高效安排见面会
- 高效率安排见面会问题(比编程之美的解法复杂度低n-1个数量级)
- 工作日志之--每日安排
- 高效能养成日记_周回顾_20130114-20
- 设计模式学习-每日一记(20.中介者模式)
- K&R 练习题 【每日一题】1-20
- 20 个快速高效学习 Java 编程在线资源
- 编程之美 - 安排见面会问题
- 每日英语:Delayed Development: 20-Somethings Blame The Brain
- 快速高效学习Java编程在线资源Top 20
- 每日学习笔记(20)
- 编程之美:第一章 1.9高效率地安排见面会
- 编程之美-----高效率地安排见面会
- 【每日分享】如何高效制造测试数据
- 合理安排时间,做个高效的人:我的开源软件“绿色报时器”