【模拟\线段树\堆】2012

背景 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)。
    这就是你需要实现的全部内容。
输入格式 Input Format
    输入的第一行有两个正整数N,M,M表示内存操作请求的个数。
    以下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。
来源 Source
    Ural 1037的增强版,与原题算法略有不同。本题作者为伪红学家。


这道题是一道很好的题。练习数据结构优化,练习代码调试能力。

题目意思有点麻烦,仔细看懂要花很大功夫。

根据题意来选择数据结构:

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值