[NOJ 1060] Countless Core Computers (线段树 or 树状数组)

题目链接:传送门

这道题本来没什么好写的,直接树状数组标记一下就能解了。

有个学弟说这道题用线段树怎么写都是MLT, 大概想了一下,对于这道题的话,线段树的内存还是能砍掉许多的。

PS:这道题的正解显然不是线段树,本文只是从"如果要用线段树的话怎么办"的角度去写,小朋友们切勿模仿。

对于树状数组:

如果要对区间[x, y]整体加上1的话,在树状数组中,让num[x]++, num[y+1]-- 即可。

树状数组在update的时候已经在求和了,所以在查询的时候,直接输出getsum(x)的值即可。

对于线段树:


对于这道题而言,每次都是在区间上加上1,因此可以预计到最终询问的时候这个值不会太大(就算某区间加了一百万次,值也只有一百万),而int的范围有42亿,可以看出这个空间太浪费了。所以可以考虑在一个int变量里存两个值。假设能成功的话,那么原本需要存1~n这样n个点的空间,现在只需要存n/2个点的空间即可。

举个例子:一共有10个点,1~10,每次给两种操作,一是区间[x, y]整体+1,二是查询某个点的值。

原本的存储空间为:

为了在一个Int内放两个数字,假设要在位置x上+t,需要这样操作:

如果x <= 5,那么num[x] += t;如果x > 5,那么num[x-5] += t * 10000。

这里的10000作为两个数字的分隔点,就好比一个大柜子中间用隔板分成两个小柜子。上层的"柜子"以一万的整数倍进行存储,下层的"柜子"按照原数字存储。

需要注意的问题是,必须保证上层和下层的"柜子"都不会溢出,本题显然无压力。

这样的话需要开辟的空间就少了一半了。

这样存好以后怎么用起来呢?

当查询的点x <= 5的时候,输出num[x] % 10000

当查询的点x > 5的时候,输出num[x] / 10000

至此成功实现了数据量缩小一半的任务。其实还能够实现空间缩小4倍,8倍之类的功能,只要保证每一个隔开的"小柜子"都不会满出来就行了。

大致写个个程序,供参考:


#include<iostream>
using namespace std;

#define maxn 100
int tree[maxn];
int num[maxn];
int part;

void build(int l, int r, int root)
{
	tree[root] = 0;
	if(l == r)
	{
		return;
	}
	int mid = (l + r)>>1;
	build(l, mid, root<<1);
	build(mid+1, r, root<<1|1);
}

void down(int l, int r, int root)   // 标记下传
{
	int mid = (l + r) >> 1;
	tree[root<<1] += (mid - l + 1) * num[root];
	num[root<<1] += num[root];

	tree[root<<1|1] += (r - mid) * num[root];
	num[root<<1|1] += num[root];

	num[root] = 0;
}

void update(int l, int r, int s, int t, int val, int root)
{
	if(s <= l && t >= r)
	{
		tree[root] += val*(r - l + 1);
		num[root] += val;
		return;
	}
	if(num[root])
		down(l, r, root);
	int mid = (l + r) >> 1;
	if(s <= mid)
		update(l, mid, s, t, val, root<<1);
	if(t > mid)
		update(mid+1, r, s, t, val, root<<1|1);
	tree[root] = tree[root<<1] + tree[root<<1|1];
}

int query(int l, int r, int pos, int root)
{
	if(l == r)
	{
		return tree[root];
	}
	if(num[root])
		down(l, r, root);
	int mid = (l + r) >> 1;
	int re1 = 0, re2 = 0;
	if(pos <= mid)
		return query(l, mid, pos, root<<1);
	else
		return query(mid+1, r, pos, root<<1|1);
}

int main()
{
	int n, m;
	int a, b;
	part = 5;  // 一共10个点,以5为分隔
	while(~scanf("%d", &n))
	{
		build(1, part, 1);
		for(int i = 0; i < n; i++)
		{
			scanf("%d%d", &a, &b);
			if(b <= part)  // 区间只在下层
			{
				update(1, part, a, b, 1, 1);
			}
			else if(a > part) // 区间只在上层
				update(1, part, a-part, b-part, 1*1000, 1);
			else   // 两层都要更新
			{
				update(1, part, a, part, 1, 1);
				update(1, part, 1, b-part, 1*1000, 1);
			}
		}
		scanf("%d", &m);
		for(int i = 0; i < m; i++)
		{
			scanf("%d", &a);
			int re = 0;
			if(a <= part)
			{
				re = query(1, part, a, 1);
				printf("re = %d\n", re%1000); // 过滤掉上层的数据,保留下层的
			}
			else
			{
				re = query(1, part, a-part, 1);
				printf("re = %d\n", re/1000); // 过滤掉下层的数据,保留上层的
			}
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值