zoukankan      html  css  js  c++  java
  • UVA

    /*
      法一借鉴自:
      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; 
    }



  • 相关阅读:
    494 Target Sum 目标和
    493 Reverse Pairs 翻转对
    492 Construct the Rectangle 构建矩形
    491 Increasing Subsequences 递增子序列
    488 Zuma Game 祖玛游戏
    486 Predict the Winner 预测赢家
    485 Max Consecutive Ones 最大连续1的个数
    483 Smallest Good Base
    Django Form组件
    Django Auth组件
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789412.html
Copyright © 2011-2022 走看看