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



posted @ 2017-09-17 06:23  mofushaohua  阅读(377)  评论(0编辑  收藏  举报