背景 Background
正如你看到的,2012年人类面临灭顶之灾,只有登上方舟的极少数人才能幸免于难。而这时你已经是得过两次IOI金牌的神牛了,因此盖茨大叔找到你,希望你给方舟上的电脑设计一套强大的操作系统Windows 2012,以完成复杂的计算。他答应给你一张头等舱的船票,你当然不能错过这个机会--除非你有10亿欧元。
描述 Description
你开始写这个操作系统,它的第一个模块自然是内存管理程序。
计算机的可用内存被划分成N个内存单元,从1至N编号。在每一时刻,每个内存单元可能是“空闲”或“占用”这两种状态中的一种。开始时,所有单元的状态是“空闲”。
系统上运行的程序在某些时刻可能提交内存操作请求,它们是“分配”或者“查询”。
分配请求是程序希望得到一个空闲的内存单元。一些分配请求指定了请求的内存单元编号,这是你只需返回这次分配是否成功。只有在该时刻那一个单元是空闲的,我们说这个操作是成功的。另一些请求没有指定内存单元,你需要分配给它一个合适的。为了使系统有更高的效率,你应该分配所有空闲内存单元中最中间的那个。换而言之,空闲的内存单元为b1,b2,…,bx,则分配的单元编号为b(x/2)(x是偶数)或b[(x+1)/2](x是奇数)。如果没有空闲内存,应该返回失败信息。
查询请求是程序希望访问某一内存单元,但你不需要考虑它的内容是什么。只有在该时刻那个内存单元是占用的,程序才能访问它。否则,我们称之为“段异常”。
另外,这些超级计算机应该非常讲求效率。所以,每个内存单元都有一定的占用期限t0(秒),由分配该内存时的请求指定。占用期从当前时刻起计。占用期限结束后,该内存自动变为空闲状态。如果在占用期限内访问该内存,占用期限变更为此时刻起的t0秒。具体地说,在t时刻占用期限为t0的内存单元,其有效期为[t,t0+t)。
这就是你需要实现的全部内容。
计算机的可用内存被划分成N个内存单元,从1至N编号。在每一时刻,每个内存单元可能是“空闲”或“占用”这两种状态中的一种。开始时,所有单元的状态是“空闲”。
系统上运行的程序在某些时刻可能提交内存操作请求,它们是“分配”或者“查询”。
分配请求是程序希望得到一个空闲的内存单元。一些分配请求指定了请求的内存单元编号,这是你只需返回这次分配是否成功。只有在该时刻那一个单元是空闲的,我们说这个操作是成功的。另一些请求没有指定内存单元,你需要分配给它一个合适的。为了使系统有更高的效率,你应该分配所有空闲内存单元中最中间的那个。换而言之,空闲的内存单元为b1,b2,…,bx,则分配的单元编号为b(x/2)(x是偶数)或b[(x+1)/2](x是奇数)。如果没有空闲内存,应该返回失败信息。
查询请求是程序希望访问某一内存单元,但你不需要考虑它的内容是什么。只有在该时刻那个内存单元是占用的,程序才能访问它。否则,我们称之为“段异常”。
另外,这些超级计算机应该非常讲求效率。所以,每个内存单元都有一定的占用期限t0(秒),由分配该内存时的请求指定。占用期从当前时刻起计。占用期限结束后,该内存自动变为空闲状态。如果在占用期限内访问该内存,占用期限变更为此时刻起的t0秒。具体地说,在t时刻占用期限为t0的内存单元,其有效期为[t,t0+t)。
这就是你需要实现的全部内容。
输入格式 Input Format
输入的第一行有两个正整数N,M,M表示内存操作请求的个数。
以下M行,前两个元素为正整数t和字符opr(为’+’或’?’),t是提交请求的时刻。操作请求按t升序排列,如有相同则按在输入中的顺序处理。
若opr为'+',这是一条分配请求。此后有两个自然数p,t0,p是指定的内存单元编号。若p为0,表示你应该分配一个合适的内存单元(如题目所述)。
若opr为'?',这是一条查询请求。此后有一个正整数q,为查询的单元号。
每行中两元素间以空格分隔,行尾无空格。输入数据是正确的,不必检验。
以下M行,前两个元素为正整数t和字符opr(为’+’或’?’),t是提交请求的时刻。操作请求按t升序排列,如有相同则按在输入中的顺序处理。
若opr为'+',这是一条分配请求。此后有两个自然数p,t0,p是指定的内存单元编号。若p为0,表示你应该分配一个合适的内存单元(如题目所述)。
若opr为'?',这是一条查询请求。此后有一个正整数q,为查询的单元号。
每行中两元素间以空格分隔,行尾无空格。输入数据是正确的,不必检验。
输出格式 Output Format
输出有M行,每行对应一条操作请求的结果。
对于分配请求,如果指定了编号,输出”+”或”-”(不含双引号,下同),分别表示成功或失败。否则,输出一个正整数,为分配的内存单元编号。
对于查询请求,输出”+”或”-”,分别表示成功或失败。
对于分配请求,如果指定了编号,输出”+”或”-”(不含双引号,下同),分别表示成功或失败。否则,输出一个正整数,为分配的内存单元编号。
对于查询请求,输出”+”或”-”,分别表示成功或失败。
样例输入 Sample Input [
复制数据]
样例输出 Sample Output [
复制数据]
时间限制 Time Limitation
时间1s。
注释 Hint
数据规模:
对于20%的数据,所有p不为0。
对于40%的数据,N<=1000,M<=5000。
对于全部的数据,1<=N<=30000,1<=M<=100000,600<=t0<=60000,1<=t<=60000,1<=q<=N。
对于20%的数据,所有p不为0。
对于40%的数据,N<=1000,M<=5000。
对于全部的数据,1<=N<=30000,1<=M<=100000,600<=t0<=60000,1<=t<=60000,1<=q<=N。
来源 Source
Ural 1037的增强版,与原题算法略有不同。本题作者为伪红学家。
这道题是一道很好的题。练习数据结构优化,练习代码调试能力。
题目意思有点麻烦,仔细看懂要花很大功夫。
根据题意来选择数据结构:
1、可以随机访问某一块内存的使用情况。初步定为线性表。
2、占用期限更新为t+t0,所以t0需要记录,同上线性表。
3、首先不考虑数据结构优化,对于找到中间的内存,一时想不到好的数据结构,决定用枚举实现。
综上可初步判断时间复杂度为O(m*n),预计能过大部分数据。
注意一个小细节,题中没有提到,就是找到中间的内存的操作,也有可能会不能找到,当且仅当内存全部被占用。
以下为朴素代码。第一次测TLE70。
考虑数据结构优化:
1、找到正中间的,因为和索引序无直接关系,但是有单调的趋势,所以我们想到有区间统计能力的线段树。
2、线段树每次需要更新,要标记空闲和忙的内存,我们需要知道哪些内存需要被释放,但是不能扫描一遍,否则就退化到O(n*m)。可以用堆来操作。
犯过的错误:
1、find中不仅要判断l==r&&tree[i]==u,而且要判断tim[i]<=t,如果只满足前者不满足后者就要继续在[l,mid)中找。因为前缀和,自己想吧。
2、adjust_up中写成while (l>0),明显l==1时不用再继续进行。
3、弹出堆的时候,只把数据复制到了根,而没有调整映射,即hash[heap[1]] = 1;
思路理清楚之后程序很简单:
这道题是一道很好的题。练习数据结构优化,练习代码调试能力。
题目意思有点麻烦,仔细看懂要花很大功夫。
根据题意来选择数据结构:
1、可以随机访问某一块内存的使用情况。初步定为线性表。
2、占用期限更新为t+t0,所以t0需要记录,同上线性表。
3、首先不考虑数据结构优化,对于找到中间的内存,一时想不到好的数据结构,决定用枚举实现。
综上可初步判断时间复杂度为O(m*n),预计能过大部分数据。
注意一个小细节,题中没有提到,就是找到中间的内存的操作,也有可能会不能找到,当且仅当内存全部被占用。
以下为朴素代码。第一次测TLE70。
#include <cstdio>
#include <string>
#include <cstring>
long t0[30010];
long tim[30010];
long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp = getchar();
while (!isdigit(tmp)&&tmp!='-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
int main()
{
freopen("2012.in","r",stdin);
freopen("2012std.out","w",stdout);
long n = getint();
long m = getint();
for (long l=1;l<m+1;l++)
{
long t = getint();
char opt;
do opt=getchar();
while (opt!='+'&&opt!='?');
if (opt == '+')
{
long p = getint();
long _t0 = getint();
if (p == 0)
{
long cnt = 0;
for (long i=1;i<n+1;i++)
{
if (tim[i] <= t)
{
cnt ++;
}
}
if (cnt == 0)
{
printf("-\n");
continue;
}
if (cnt & 1)
{
cnt = (cnt+1)>>1;
}
else
{
cnt = cnt>>1;
}
for (long i=1;i<n+1;i++)
{
if (tim[i] <= t)
{
cnt --;
if (!cnt)
{
p = i;
break;
}
}
}
printf("%ld\n",p);
tim[p] = t + _t0;
t0[p] = _t0;
}
else if (tim[p] <= t)
{
printf("+\n");
tim[p] = t + _t0;
t0[p] = _t0;
}
else
{
printf("-\n");
}
}
else
{
long q = getint();
if (t < tim[q])
{
printf("+\n");
tim[q] = t+t0[q];
}
else
{
printf("-\n");
}
}
}
return 0;
}
考虑数据结构优化:
1、找到正中间的,因为和索引序无直接关系,但是有单调的趋势,所以我们想到有区间统计能力的线段树。
2、线段树每次需要更新,要标记空闲和忙的内存,我们需要知道哪些内存需要被释放,但是不能扫描一遍,否则就退化到O(n*m)。可以用堆来操作。
犯过的错误:
1、find中不仅要判断l==r&&tree[i]==u,而且要判断tim[i]<=t,如果只满足前者不满足后者就要继续在[l,mid)中找。因为前缀和,自己想吧。
2、adjust_up中写成while (l>0),明显l==1时不用再继续进行。
3、弹出堆的时候,只把数据复制到了根,而没有调整映射,即hash[heap[1]] = 1;
思路理清楚之后程序很简单:
#include <cstdio>
#include <string>
#include <cstring>
long t0[30010];
long tim[30010];
long heap[120010];
long hash[30010];
long size = 0;
long tree[120010];
long t = 0;
long swap(long a,long b)
{
long tmp = hash[heap[a]];
hash[heap[a]] = hash[heap[b]];
hash[heap[b]] = tmp;
tmp = heap[a];
heap[a] = heap[b];
heap[b] = tmp;
}
long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp = getchar();
while (!isdigit(tmp)&&tmp!='-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}
void build(long l,long r,long i)
{
long mid = (l+r)>>1;
if (l == r)
{
tree[i] = 1;
return;
}
build(l,mid,i<<1);
build(mid+1,r,(i<<1)+1);
tree[i] = tree[i<<1]+tree[(i<<1)+1];
}
void erase(long l,long r,long i,long u)
{
long mid = (l+r)>>1;
if (l == r)
{
tree[i] = 1;
return;
}
else if (u <= mid)
{
erase(l,mid,i<<1,u);
}
else
{
erase(mid+1,r,(i<<1)+1,u);
}
tree[i] = tree[i<<1]+tree[(i<<1)+1];
}
void insert(long l,long r,long i,long u)
{
long mid = (l+r)>>1;
if (l == r)
{
tree[i] = 0;
return;
}
else if (u <= mid)
{
insert(l,mid,i<<1,u);
}
else
{
insert(mid+1,r,(i<<1)+1,u);
}
tree[i] = tree[i<<1]+tree[(i<<1)+1];
}
long find(long l,long r,long i,long u)
{
if (l == r && tree[i] == u && tim[l]<=t)
{
return l;
}
long mid = (l+r)>>1;
if (tree[i<<1] >= u)
{
return find(l,mid,i<<1,u);
}
else
{
return find(mid+1,r,(i<<1)+1,u-tree[i<<1]);
}
}
void adjust_down(long l)
{
while ((l<<=1)<size+1)
{
if (l+1<size+1&&tim[heap[l]]>tim[heap[l+1]])l++;
if (tim[heap[l]]<tim[heap[l>>1]])swap(l,l>>1);
else break;
}
}
void adjust_up(long l)
{
while (l>1)
{
if (tim[heap[l]]<tim[heap[l>>1]])swap(l,l>>1);
else break;
l >>= 1;
}
}
void pop()
{
if (size > 0)
{
heap[1] = heap[size];
hash[heap[1]] = 1;
size --;
adjust_down(1);
}
}
void push(long a)
{
heap[++size] = a;
hash[a] = size;
adjust_up(size);
}
void modify(long l)
{
adjust_down(hash[l]);
}
int main()
{
freopen("2012.in","r",stdin);
freopen("2012.out","w",stdout);
long n = getint();
long m = getint();
build(1,n,1);
for (long l=1;l<m+1;l++)
{
t = getint();
char opt;
do opt=getchar();
while (opt!='+'&&opt!='?');
while (size>0&&tim[heap[1]] <= t)
{
long u = heap[1];
erase(1,n,1,u);
pop();
}
if (opt == '+')
{
long p = getint();
long _t0 = getint();
if (p == 0)
{
long cnt = tree[1];
if (cnt == 0)
{
printf("-\n");
continue;
}
if (cnt & 1)
{
cnt = (cnt+1)>>1;
}
else
{
cnt = cnt>>1;
}
long p = find(1,n,1,cnt);
printf("%ld\n",p);
tim[p] = t + _t0;
t0[p] = _t0;
push(p);
insert(1,n,1,p);
}
else if (tim[p] <= t)
{
printf("+\n");
tim[p] = t + _t0;
t0[p] = _t0;
push(p);
insert(1,n,1,p);
}
else
{
printf("-\n");
}
}
else
{
long q = getint();
if (t < tim[q])
{
printf("+\n");
tim[q] = t+t0[q];
modify(q);
}
else
{
printf("-\n");
}
}
}
return 0;
}