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; 
    }



  • 相关阅读:
    PHP 包含文件路径问题
    PHP显示今天、今月、上月、今年的起点/终点时间戳
    PHP Web基础教程
    php如何生成随机密码的几种方法
    You don't have permission to access /~whpc on this server.
    linux 网络 PING IP可以通,ping域名ping不通
    CPU : 二级缓存容量
    简单建立个人WEB网站
    MKL程序编译与连接:Lapack篇
    ldd命令原理与使用
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789412.html
Copyright © 2011-2022 走看看