UVA - 822 Queue and A
/* 法一借鉴自: http://blog.csdn.net/wangjinhaowjh/article/details/50551640 */ #include <iostream> #include <vector> #include <map> #include <algorithm> #define rep(i, n) for (int i = 0; i < (n); i++) using namespace std; struct topic { int tid, num, t0, t, dt, num1; //num1为该主题最初的请求个数,num为当前剩余的请求个数 }; struct person { int pid, k, last, busy, time; // last为客服上次处理请求的时间,busy标记是否忙碌,time为当前处理任务的剩余时间(不忙碌时为0) vector<int> tidk; // tidk保存客服可以处理的所有请求主题 ( 保存的是主题的 tid ) bool operator < (const person& a) const { if (last != a.last) last < a.last; else pid < a.pid; } }; vector<topic> topics; vector<person> people; map<int, int> rel; // relationship key为tid,value为该主题在topics容器中的下标(从0开始) map<int, vector<person> > staff; // key为任务主题的tid,value为选定同一主题的所有客服 map<int, int> service;//key为客服的标识符pid,value为客服的标号(从0开始) int if_solve(const topic &x, int t) //i 为当前处理任务的标号(非tid),t为当前时间 { if (t < x.t0) return 0; //不能比处理第一个请求的时间早 if (!x.dt) return x.num > 0; //如果该主题的请求连续不间断,则只要当前还需处理的数目大于0,就一定能处理 if (!x.num) return 0; //请求数已为0,一定不能处理该主题的任务了 if ((t - x.t0) / x.dt == (x.num1 - x.num - 1) ) return 0; //注意 (x.num1 - x.num - 1) 一定要加括号,否则会因为运算符的优先级而WA return 1; } int main() { int n, m, kase = 0; int i, j, k; while (cin >> n && n) { int time = 0; topics.clear(); people.clear(); rel.clear(); service.clear(); rep(i, n) //输入n种主题及其对应信息 { topic a; cin >> a.tid >> a.num >> a.t0 >> a.t >> a.dt; a.num1 = a.num; topics.push_back(a); rel[a.tid] = i; //将主题的tid,与它在tipics中的下标建立对应关系 } cin >> m; rep(i, m) //输入m个客服及其对应信息 { person a; a.tidk.clear(); a.last = a.busy = a.time = 0; cin >> a.pid >> a.k; service[a.pid] = i; int tid; rep(j, a.k) { cin >> tid; a.tidk.push_back(tid); } people.push_back(a); } for (time = 0; ; time++) { staff.clear(); rep(i, m) //找到m个客服中,不忙的客服进行循环 if (!people[i].busy) rep(j, people[i].k) //遍历该客服可处理的k种类主题,并按优先级找到第一个可处理的主题 { int &tp = people[i].tidk[j]; //tp为,第i个客户可处理的优先级排第j的请求主题的tid if ( if_solve(topics[rel[tp]], time) ) { if ( !staff.count(tp) ) staff[tp] = vector<person>(); staff[tp].push_back(people[i]); break; //每个有空的人,只要找到第一个可以处理的请求即可 } } map<int, vector<person> > ::iterator it; for (it = staff.begin(); it != staff.end(); it++) {//*it 为 pair(tid, vector<person>) sort ( it->second.begin(), it->second.end() ); int tp = service[it->second[0].pid]; // tp保存:对于同一个主题的tid,如果多人同时选择,最终处理该主题的客服是谁,并通过service的映射对应到该客户在people容器中的下标 people[tp].busy = 1; people[tp].last = time; people[tp].time = topics[rel[it->first]].t; //该客户执行任务的剩余时间,被赋值为对该主题,处理一个请求的时间 topics[rel[it->first]].num--; //所处理主题对应的该主题请求个数自减 } //对于每一个在处理任务的人,更新其任务的剩余时间 rep(i , m) { if (people[i].busy) people[i].time--; if (!people[i].time) people[i].busy = 0; } int flag = 1; //如果所有人都处理完了任务,并且所有主题的剩余请求个数为0,那么可以退出,但是真正的处理完毕时间,还要在time的基础上加1 rep(i, n) if (topics[i].num > 0) { flag = 0; break; } rep(i, m) if (people[i].time > 0) { flag = 0; break; } if (flag) break; } cout << "Scenario " << ++kase << ": All requests are serviced within " << time+1 << " minutes." << endl; } return 0; }
/* 法二(做完后去看了别人的方法): http://blog.csdn.net/a197p/article/details/44199713 与法一相似,但也有不同,主要体现在 1. 取消了rel这个映射(原来是用来建立主题的tid和其在topics中下标的键值关系),取消了service(原来建立客服标识符pid和客服标号之间的键值关系); 该博主如何处理客服选择当前回复的主题? 1.1 排序所有客服(客服排序的标准:多个人同时处理一个任务时,该任务被谁处理,谁排前面) 1.2 对每个客服,按照其可执行任务的优先级循环一次(j循环),再按当前所有剩余任务执行一次(k循环),如果遇到第一个可以处理的任务(tid相同,且客服手头没有别的任务),则做 1.3 中的处理以后,循环到下一个客服,遍历完所有客服后执行1.4,如果没遇到,则在双层循环遍历完staff[i].pidk和topics中的所有主题,也会继续循环其他的客服 1.3 更新客服上次执行任务的时间、当前剩余时间,所接任务主题的剩余请求数,和need(need的含义:当前时刻下,执行时间最久的任务,执行完需要多久);此外,对于某个主题,如果请求数都被处理完(finished == num),则该主题可从主题的容器topics中删去,如果所有主题都执行完了,只要将当前时间,加上当前时间最长的任务还需要多久,便可退出循环,进行输出了 1.4 循环完所有客服以后,没退出则时间自增,need(need的含义:当前时刻下,执行时间最久的任务,执行完需要多久)自减直至为0,每一个客服的当前任务剩余时间自减直到为0 优点:map的键值关系比较少,降低了混淆的可能性,并且在排序和遍历时,省去了迭代器和其指针操作->,在对这些理解不太深刻时,用这种方法比较不易出错 缺点:三重循环,在数据很多时,容易TLE */
/* 法三(做完后去看了别人的方法,并且自己也按照这个思路写了一次): http://www.cnblogs.com/xienaoban/p/6798081.html 该博主的思路十分巧妙,虽然我看了许久许久才看懂,但真的想说,他的解法很精妙,值得反刍 并且,速度还从法一的 100ms,降到了法二的 10ms,可见法二在效率上也很令人惊叹 收获 || 总结: 1. C++ 用括号初始化变量(这种写法之前只是在 class 的构造函数的初始化列表中学到过,但是对于一般的变量,也是可以在其后面用括号初始化的) 如: int Scenario(0); 此外,关于C++初始化的小总结,以及C++11新增的新的初始化方式: http://blog.csdn.net/k346k346/article/details/55194246 2.另外,该博主不是按时间一秒一秒增加来模拟的,而是每次都找出所有人的,状态变化临界点(从工作到休息,或是从休息到工作,的状态切换时间点),取其中的最小值,就能兼顾到所有人组成的整体的状态切换,这个角度十分巧妙,将以时间为主线考虑,变为了以人的工作状态为主线考虑 3. 没有用太多map,结构体中也没存那么多数据,仅存最关键的核心数据 (例如对于主题而言,重要的是每次处理所需的时间,以及每次请求分别在哪些时刻发出,因为只有发出请求以后,该请求才能被客服处理) 并且每次主题请求为空,直接出栈,且主题数自减,当减为0时,循环结束,运用queue的压入弹出、是否为空的判断,代替对主题和对客服判断的循环,提高效率 4. 此外,STL存在性能瓶颈,例如对map使用map[key]时,每次都需要重新查找,在这题中,博主的思路,就是将查找结果赋值给一个引用变量,这样接下来就不再需要每次用前都查找,提高了效率 作者其他的思路可见原博主代码后的解析 */
#include <iostream> #include <vector> #include <queue> #include <algorithm> #include <map> #define rep(i, n) for (int i = 0; i < (n); i++) const int INF = 0x3f3f3f3f; using namespace std; struct topic { int t; //记录处理一个请求所用的时间 queue <int> arrival; //arrival为topic的num个请求的依次抵达时间 }_topic; struct person { int pid, next, last, k, tidk[22]; //next为下次有空的时间,last为上次开始处理主题的时间 bool operator < (const person & t) const { if (last != t.last) return last < t.last; return pid < t.pid; } }_person; int n, m; //topic个数和客服人数 int tid, num, t0, dt, kase(0); map<int, topic>rel; //主题的tid与主题的对应关系 vector<person> people; int main() { cin.tie(0); cin.sync_with_stdio(false); _person.next = 0; while (cin >> n && n) { int time(INF), needtime(0); rel.clear(); people.clear(); rep(i, n) { cin >> tid >> num >> t0 >> _topic.t >> dt; time = min(time, t0); //time记录当前开始时间的最小值 auto &now(rel[tid] = _topic); //用引用,减少map查找的时间,提高效率 rep(i, num) //该主题的所有请求时间,压入tidk { now.arrival.push(t0); t0 += dt; } } cin >> m; rep(i, m) { cin >> _person.pid >> _person.k; rep(j, _person.k) cin >> _person.tidk[j]; people.push_back(_person); } while (n) { int jumpt = INF; //jumpt其实含义为:对整体而言,每次最早出现的状态变化临界点 sort(people.begin(), people.end()); for (auto &p : people) { int pti(INF); //pti的意义:开始空闲”或“可能有事情做”的时间,即为可能的状态变化临界时间点(之所以是“可能”,因为每次都要按照“上一次开始干活的时间与ID”把每个人排序,所以他的活可能被抢,依然是“无事可干”状态) if (p.next > time) pti = p.next; //若这人正在处理一个topic,则pti=处理完本topic的时间 else { rep(i, p.k) { auto &t(rel[p.tidk[i]]); if (t.arrival.empty()) continue; if (t.arrival.front() <= time) //表示在当前时间,确实有最近的请求没有客服处理,则该客服处理,队首请求出队 { pti = time + t.t; //处理该主题耗时t needtime = max(needtime, pti); //needtime用来表示处理完毕的时刻,每次都是在现有时间上,加上该主题的处理时间,如果比原来的timeneed大,则更新timeneed,这样可以保证,needtime时刻,所有人手头任务完成,所有请求处理完毕 p.last = time; t.arrival.pop(); if (t.arrival.empty()) n--; //某个主题的请求为空,则请求数自减 break; } else if (t.arrival.front() < pti) pti = t.arrival.front(); } p.next = pti; //更新当前的人的下一次状态变化临界时间 } jumpt = min(pti, jumpt); //找到所有人的pti中的最小值,因为pti的最小值是整体状态变化的临界点(所谓状态变化,是指一个人从没处理主题到处理主题,或是从正在处理变为空闲),将可能存在状态变化的最小临界时间点(也就是所有人的pti中,最小的pti),作为新一轮循环的时间 } time = jumpt; } cout << "Scenario " << ++kase <<": All requests are serviced within " << needtime << " minutes." << endl; } return 0; }