#洛谷 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

说明/提示

对于20%的数据,n,m≤5000n,m\leq 5000n,m≤5000

对于40%的数据,n,m≤105n,m\leq 10^5n,m≤105

对于60%的数据,n,m≤5×105n,m\leq 5\times 10^5n,m≤5×105

对于所有数据,n,m≤1×106n,m\leq 1\times 10^6n,m≤1×106

本题可能需要较快的读入方式,最大数据点读入数据约20MB

题目大意 : 有一个长为 N 的序列,M次询问, 每次询问一个区间有多少个不同的数

思路 : 在线 + 主席树,离线 + 树状数组,莫队,这三种方法都可以写, 这里只介绍树状数组的(最简单的就是这个了)

首先需要了解一个东西, 查询区间内某数出现的次数, 该数一定只放在最后一次出现的位置, 就拿样例来说, N = 7, 序列为1, 3, 4, 5, 7, 1, 9。 1这个数在第一个位置和第6个位置都有出现, 但是对于一个右端点大于6的区间, 我们只算第6个位置的次数(想想就明白了), 基于这样一种性质, 每次一个数出现后, 如果是第一次出现, 就将该位置的数的次数 + 1, 否则将原位置的数次数 - 1, 该位置 + 1, 但是这样一来就只能一直往右更新了, 否则你后面再查询到左边的区间, 那个你已经更新过了,所以需要对询问进行排序, 按照右区间从小到大, 再结合原序列, 依次处理就好

Accepted code 

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;

#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define MEM(x, b) memset(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e6 + 100;
const int INF = 0x3f3f3f3f;
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % MOD; b >>= 1; t = (t*t) % MOD; }return r; }

struct node
{
	int l, r, ori;
}t[MAXN];
bool cmp(node a, node b) {
	return a.r < b.r;
}
int p[MAXN], c[MAXN], n, m;
int res[MAXN], vis[MAXN];  // res为离线后的答案, vis为该数上一次的出现位置
void add(int x, int y) {
	while (x <= n) {
		c[x] += y;
		x += lowbit(x);
	}
}
int Query(int x) {
	int ans = 0;
	while (x) {
		ans += c[x];
		x -= lowbit(x);
	}
	return ans;
}

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) sc("%d", &p[i]);
	cin >> m;
	for (int i = 0; i < m; i++) {
		sc("%d %d", &t[i].l, &t[i].r);
		t[i].ori = i;
	}
	sort(t, t + m, cmp);
	int tmp = 1;  // 更新到了哪个位置
	for (int i = 0; i < m; i++) {
		int L = t[i].l, R = t[i].r;
		for (int j = tmp; j <= R; j++) {   // 从上一次位置开始更新
			if (vis[p[j]]) add(vis[p[j]], -1);   // 出现过就删去原位置
			vis[p[j]] = j; add(j, 1);   // 新位置加上
		}
		int scnt = Query(R) - Query(L - 1);
		res[t[i].ori] = scnt;   // 保存答案
		tmp = R + 1;  
	}
	for (int i = 0; i < m; i++) printf("%d\n", res[i]);
	return 0;
}

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值