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

题目来源:HH的项链

题目大意:

给定一串项链,每个数字代表一种贝壳,给出多个询问区间,求区间内贝壳的种类数量

解题思路:离线 + 树状数组

1.一个结论

首先我们要知道一个结论,对于许多个右端点相同的询问,总是先考虑在右边的种类对答案的贡献,什么意思?,我们看一个例子

例如:1 1 4 5 1 4(嘿嘿)

对于所有右端点为6的查询,前两个1完全没有意义,1对答案的贡献就可以只考虑第3个1,前两个1完全可以用第3个1来代替

所以我们就可以对所有询问以右端点进行排序,然后维护一个树状数组

2.树状数组维护什么?

树状数组维护的是当前询问区间内种类对答案的贡献,由于之前对查询进行了排序,就可以一边遍历询问(排序后的)一边维护树状数组

我们直接看一个例子:

首先题目中的查询已经排好序了,然后我们遍历询问

<1>  对于询问[1,2] ,我们可以定义一个next来遍历原数组,next的范围小于当前的r,

首先对于1,没有出现过,进行add(1,1),定义数组pos保存这个1的位置,并代表1出现过,对于2同样没有出现过,进行相同的操作,此时树状数组所表示的每个位置上的数字就为:1 1 0 0 0 0

询问1的答案就为sum(2) - sum(1 - 1)  = 2 - 0 = 2,我们开一个ans数组把答案存入

操作完成我们令next = r + 1

<2>  对于询问[3,5]

首先对于3和4都没有出现过,我们用上述操作,之后树状数组每个位置的值就为: 1 1 1 1 0 0

然后我们看第5个元素为3,之前出现过,也就是pos数组中记录过上一个3的位置,从上面的结论,就应该只考虑最新出现的种类的贡献,所以我们要进行操作add(pos[3],-1),取消上一个3对答案的贡献,然后再重新add(5,1)记录当前3的贡献并用pos记录位置.

那么此时树状数组每个位置的值就为: 1 1 0 1 1 0

询问2的答案就为sum(5) - sum(3 - 1) = 4 - 2 = 2

询问3也相同

于是这部分的代码就可以这样写:

    int next = 1; 
    for (int i = 1;i <= m; i++) { // m为先想询问个数
    	for (int j = next;j <= q[i].r; j++) {
    		if (pos[a[j]]) add(pos[a[j]],-1); // 前面出现过,取消前面的贡献
    		add(j,1); // 记录贡献
    		pos[a[j]] = j; // 记录位置
    	}
    	next = q[i].r + 1;
    	// 记录答案
    	ans[q[i].id] = sum(q[i].r) - sum(q[i].l - 1); 
    }

3.一些注意点:

1.要记得排序,给的例子是刚好排好序了

2.存答案要按询问序号存,因为按右端点排序之后,序号也排了

4.ACcode

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

using ll = long long ;

const int N = 1e6 + 50;
struct node {
	int l,r,id; // 左右端点和序号
	bool operator < (const node a) const { // 结构体的重载
		return r < a.r;
	} 
}q[N]; // 存询问
int n,m;
int a[N]; // 原数组
int tree[N]; // 树状数组
int pos[N]; // 记录元素的位置
int ans[N]; // 记录答案
//------------树状数组板子-----------//
int lowbit(int i) {return i&-i;}

void add(int x,int val) { // 修改操作
	while (x <= n) {
		tree[x] += val;
		x += lowbit(x);
	}
}

int sum(int i) { // 求和
	int ret = 0;
	while (i) {
		ret += tree[i];
		i -= lowbit(i);
	}
	return ret;
}
//------------------------------//

int main() {
	cin >> n;
	for (int i = 1;i <= n; i++) cin >> a[i];
    cin >> m;
    for (int i = 1;i <= m; i++) {
    	cin >> q[i].l >> q[i].r;
    	q[i].id = i; // 记录询问顺序
    } 
    sort(q + 1,q + m + 1) ; // 按右端点排序
    int next = 1; // 遍历原数组
    for (int i = 1;i <= m; i++) {
    	for (int j = next;j <= q[i].r; j++) {
    		if (pos[a[j]]) add(pos[a[j]],-1);
    		add(j,1);
    		pos[a[j]] = j;
    	}
    	next = q[i].r + 1;
    	// 记录答案
    	ans[q[i].id] = sum(q[i].r) - sum(q[i].l - 1); 
    }
    for (int i = 1;i <= m; i++) cout << ans[i] << '\n';
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值