POJ1193 [NOI1999]内存分配

这道题的正解貌似是链表,但是我用了set代替链表的功能,于是乎调了有两三天。
做本题的心路历程

思路本质

%你

大体思路

观察题面和数据范围。
需要维护的信息比较复杂,大致分两部分:

  1. 正在执行任务的程序
    对于这部分数据,我们需要储存它们的
    1. 结束时间
    2. 占用内存区间
  2. 正在等待队列中的程序
    对于这部分数据,我们需要储存
    1. 占用时长
    2. 占用区间长度

在数据范围上,总体花的时间、空间在1e9级别,不可能全部维护。
而离散化也很困难,所以考虑类似珂朵莉树的做法:将可用区间缩为一个记录了左右端点的节点,用set(平衡树)或list(链表)保证从左到右储存,使用时就直接对节点进行简单的编辑。
总体需要维护以下三个结构:

  1. 等待队列
  2. 正在执行的任务,用堆(具体采用priority_queue)维护最先结束的任务
  3. 可用的内存区间,我选择用平衡树(set)从左到右维护

完成以上基本定义后,接下来具体做法有两种

  1. 以请求作为分界
    这是我一开始的做法,就是在读入的同时进行操作,读入完成前以每一个请求作为一个阶段,每读入一个请求就检测在那之前有多少个任务结束,又有多少个正在等待的任务已经开始;读入完成后再以每一个等待队列中的任务作为分界线……然而这样做维护起来非常困难,细节问题非常多。
  2. 以请求和任务的结束分界
    这是我后来AC的做法。
    先将所有的请求读入(其实边读边做也行?),然后每一次选择下一个请求或者执行中任务的结束进行计算,取两者先发生的一个。
    1. 如果下一个事件是某个任务的结束
      1. 我们把它和同一刻结束的任务从正在执行任务的堆中弹出,每弹出一个都要释放它所占用的空间
      2. 检测等待队列中的队首任务是否可以马上执行
      3. 若可以,执行并检测下一个正在等待的任务,直到队列空掉或者队首不再可以执行
    2. 如果是某个请求
      检测它是否可以直接执行,不行就丢进等待队列。

具体实现

储存结构

内存:

struct interval
{
	int l,r;//左右
};
bool operator <(const interval &a,const interval &b)//从左到右
{return a.l<b.l;}
typedef set<interval>::iterator it;//for循环用
set<interval>s;

正在执行

struct task
{
	int l,r,end;//[start,end]
	//占用内存区间[l,r]到end
	//end+1时已不再占用
};
bool operator >(const task &a,const task &b)
{return a.end>b.end;}//以结束时间排序
priority_queue<task,vector<task>,greater<task> >q;//小根堆

等待队列

struct wait
{
	int size,time;//占用长度,占用时间
};
queue<wait>hang;

还有一个请求队列:

struct request
{
	int st,size,time;//请求发出时间,请求内存大小,占用时间
};
queue<request>reqs;

释放一个区间

void free(interval x)//x是一个区间
{
	if(s.size()==0){s.insert((interval){x.l,x.r});return;}//如果没有空地直接插入就好了

	//找到它左边的节点
	it l=s.lower_bound(x);
	if(l!=s.begin())//待会要讲,重点
		--l;

	if(l->r==x.l-1)//如果和它连在一起
	{
		int L=l->l;
		s.erase(l);
		l=s.insert((interval){L,x.r}).first;//合并两者
	}
	else l=s.insert((interval){x.l,x.r}).first;
	it r=s.upper_bound(x);//它右边的节点
	if(r->l==x.r+1)
	{
		int L=l->l,R=r->r;
		s.erase(l);
		s.erase(r);
		s.insert((interval){L,R});//合并
	}
}

占用一个区间

void use(it x,int size)//占用x节点当中最左边size格内存
{
	int L=x->l,R=x->r;
	s.erase(x);
	if(L+size<=R)//如果还有用剩的
		s.insert((interval){L+size,R});
}

主函数

int main()
{
	int n;
	scanf("%d",&n);
	s.insert((interval){1,n});
	int st,size,time;
	while(scanf("%d%d%d",&st,&size,&time),st||size||time)//读入
		reqs.push((request){st,size,time});
	int now,cnt=0;
	it i;
	next:
	while(reqs.size()||hang.size()||q.size())
	{
		if(!q.size()/*没有在执行的任务*/||reqs.size()/*有待处理的请求*/&&reqs.front().st<q.top().end/*请求更早,同时的话先处理等待队列*/)
		//处理请求
		{
			st=reqs.front().st;
			size=reqs.front().size;
			time=reqs.front().time;
			for(i=s.begin();i!=s.end();i++)//找个区间把它塞进去
				if(i->r-i->l+1>=size)
				{
					q.push((task){i->l,i->l+size-1,st+time-1});
					use(i,size);
					reqs.pop();
					goto next;//懒得打bk了,跳到next:那一行
				}
			hang.push((wait){size,time});//把它挂起来,进入咕咕队列
			reqs.pop();
			cnt++;
		}
		else//回收内存并处理等待队列
		{
			now=q.top().end+1;
			while(q.size()&&q.top().end<now)
			{
				free((interval){q.top().l,q.top().r});
				q.pop();
			}
			next2:
			if(hang.size())
			{
				for(i=s.begin();i!=s.end();i++)
					if(i->r-i->l+1>=hang.front().size)
					{
						size=hang.front().size;
						time=hang.front().time;
						st=now;
						q.push((task){i->l,i->l+size-1,st+time-1});
						use(i,size);
						hang.pop();
						goto next2;//处理下一个等待
					}
			}
		}
	}
	printf("%d\n%d",now,cnt);
	return 0;
}

卡了我两天的细节

我在上面标了一处代码

	//找到它左边的节点
	it l=s.lower_bound(x);
	if(l!=s.begin())//待会要讲,重点
		--l;

这就是我调了三天还在电脑上下了个VirtualBox的原因
有一种可能是这样的:s.lower_bound(x)==s.begin()
我们来看看zh.cppreference.com是怎么说的:

GG
让我们来欣赏一下这一行带来的结果:

还有很多类似的情况,比如当一个容器为空的时候访问它的元素
比如

vector<int>v;
cout<<v[1];//未定义行为

而往往这些情况会导致莫名的RE,有些情况下虽然在调样例时就可以看出,但是也有些情况隐藏的很深……
所以以后我们在用STL时一定要注意各种边界,因为它里面的未定义行为和各种满天飞的指针实在是太多了,而且不同的编译器又会给出不同的实现,从而导致不同的、令人困扰结果……从而耽误你很多时间,在考场上面还会耽误你很多分数。
所以如果不是时间很紧就尽量少用STL吧。

重(dai)点(ma)

有多少人只是来抄代码的自己知道

#include<cstdio>
#include<set>
#include<queue>
using namespace std;
struct interval
{
	int l,r;//左右
};
bool operator <(const interval &a,const interval &b)//从左到右
{return a.l<b.l;}
typedef set<interval>::iterator it;//for循环用
set<interval>s;
struct task
{
	int l,r,end;//[start,end]
	//占用内存区间[l,r]到end
	//end+1时已不再占用
};
bool operator >(const task &a,const task &b)
{return a.end>b.end;}//以结束时间排序
priority_queue<task,vector<task>,greater<task> >q;//小根堆
struct wait
{
	int size,time;//占用长度,占用时间
};
queue<wait>hang;
struct request
{
	int st,size,time;//请求发出时间,请求内存大小,占用时间
};
queue<request>reqs;
void free(interval x)//x是一个区间
{
	if(s.size()==0){s.insert((interval){x.l,x.r});return;}//如果没有空地直接插入就好了

	//找到它左边的节点
	it l=s.lower_bound(x);
	if(l!=s.begin())//待会要讲,重点
		--l;

	if(l->r==x.l-1)//如果和它连在一起
	{
		int L=l->l;
		s.erase(l);
		l=s.insert((interval){L,x.r}).first;//合并两者
	}
	else l=s.insert((interval){x.l,x.r}).first;
	it r=s.upper_bound(x);//它右边的节点
	if(r->l==x.r+1)
	{
		int L=l->l,R=r->r;
		s.erase(l);
		s.erase(r);
		s.insert((interval){L,R});//合并
	}
}
void use(it x,int size)//占用x节点当中最左边size格内存
{
	int L=x->l,R=x->r;
	s.erase(x);
	if(L+size<=R)//如果还有用剩的
		s.insert((interval){L+size,R});
}
int main()
{
	int n;
	scanf("%d",&n);
	s.insert((interval){1,n});
	int st,size,time;
	while(scanf("%d%d%d",&st,&size,&time),st||size||time)//读入
		reqs.push((request){st,size,time});
	int now,cnt=0;
	it i;
	next:
	while(reqs.size()||hang.size()||q.size())
	{
		if(!q.size()/*没有在执行的任务*/||reqs.size()/*有待处理的请求*/&&reqs.front().st<q.top().end/*请求更早,同时的话先处理等待队列*/)
		//处理请求
		{
			st=reqs.front().st;
			size=reqs.front().size;
			time=reqs.front().time;
			for(i=s.begin();i!=s.end();i++)//找个区间把它塞进去
				if(i->r-i->l+1>=size)
				{
					q.push((task){i->l,i->l+size-1,st+time-1});
					use(i,size);
					reqs.pop();
					goto next;//懒得打bk了,跳到next:那一行
				}
			hang.push((wait){size,time});//把它挂起来,进入咕咕队列
			reqs.pop();
			cnt++;
		}
		else//回收内存并处理等待队列
		{
			now=q.top().end+1;
			while(q.size()&&q.top().end<now)
			{
				free((interval){q.top().l,q.top().r});
				q.pop();
			}
			next2:
			if(hang.size())
			{
				for(i=s.begin();i!=s.end();i++)
					if(i->r-i->l+1>=hang.front().size)
					{
						size=hang.front().size;
						time=hang.front().time;
						st=now;
						q.push((task){i->l,i->l+size-1,st+time-1});
						use(i,size);
						hang.pop();
						goto next2;//处理下一个等待
					}
			}
		}
	}
	printf("%d\n%d",now,cnt);
	return 0;
}

这真是我写的最认真的一篇题解了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值