洛谷P1972[SDOI2009]HH的项链(树状数组)

题目描述

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

输入输出格式

输入格式:

 

第一行:一个整数N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。

第三行:一个整数M,表示HH 询问的个数。

接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

 

输出格式:

 

M 行,每行一个整数,依次表示询问对应的答案。

 

输入输出样例

输入样例#1: 

6
1 2 3 4 3 5
3
1 2
3 5
2 6

输出样例#1: 

2
2
4

说明

数据范围:

对于100%的数据,N <= 500000,M <= 500000。

解题思路

 用树状数组求区间[x,y]的和时,是用query(y)-query(x-1),前y个值的和减去前x-1个。按照这个思路,在以y为r的区间里,关键的是一个元素出现的最右边的位置,因为左边会被减掉。所以如果存在重复出现的数,只要保留右边这个就可以了,把左边的去掉。

比如说用tree[]来维护b[](b[i]为1时表示a[i]这个元素值在当前区间内出现的最右位置为i,这句话有点绕,看下去就知道了),先把两个数组都初试化为0,如果要处理的贝壳序列是 1 3 3 2 2,先处理第一个数1,因为前面不存在1,那么就把b[1]+=1。第二个数3,因为前面不存在3,那么就把b[2]+=1。第三个数3,因为在位置2已经有一个3(开个last[]数组存上一个i出现的位置),但是在区间[1,3]里,位置3的3才是最右边的3,所以把b[2]-=1,b[3]+=1。因为左边重复的都已经变成0了,所以直接query(y) - query(x-1)就没问题了。

还有就是因为需要查询的指令很多,所以要存一下,然后按照r的值排序,因为输出答案的时候还是要按照对应输入的次序输出,所以也要存一下,不然排完序就乱了。排完序遍历指令扫过去一遍,开个存答案的数组把对应次序的答案按下标存进去,最后输出就好了。

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cstdio>
using namespace std;
int tree[500005];   //树状数组 
int a[500005];    //原数组 
int last[1000005];  //i上一次出现的位置 
int ans[500005];  //答案数组 
struct T{
	int l, r, i;
	T() = default;
	T(int l, int r, int i): l(l), r(r), i(i){	}
}ins[500005];   //记录询问区间以及此区间排在第几个 
bool cmp(T a, T b)
{
	return a.r < b.r;
}
int n;
int lowbit(int x)
{
	return x & (-x);
}
void update(int x, int k)  //更新 
{
	for(int i = x; i <= n; i += lowbit(i))
		tree[i] += k;
}
int query(int x)   
{
	int sum = 0;
	for(int i = x; i > 0; i -= lowbit(i))
		sum += tree[i];
	return sum;
}
int main()
{
	while(cin >> n){
		for(int i = 1; i <= n; i ++)
			scanf("%d", &a[i]);
		memset(tree, 0, sizeof(tree));;
		memset(last, 0, sizeof(last));
		int m;
		cin >> m;
		for(int i = 0; i < m; i ++){
			int x, y;
			scanf("%d%d", &x, &y);
			ins[i] = T(x, y, i);
		}
		sort(ins, ins + m, cmp);
		int s = 1;
		for(int i = 0; i < m; i ++){
			for(; s <= ins[i].r; s ++){
				int num = a[s];
				if(last[num]){
					update(last[num], -1);
				}
				update(s, 1);
				last[num] = s;
			}
			ans[ins[i].i] = query(ins[i].r) - query(ins[i].l - 1);
		}
		for(int i = 0; i < m; i ++)
			printf("%d\n", ans[i]);
	}
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值