[TK] HH的项链 离线树状数组解法

实际上这题很难和树状数组联系起来,我感觉效率也不是很高,感觉不是正解

怎么使用树状数组

这道题我们很容易想到一点:同种物品在一个区间内只能出现一次,先不考虑别的问题,我们想出下面这种使用树状数组的思路:

  • 定义树状数组只有 \(0\)\(1\) 两种状态,分别表示统计这个物品或不统计这个物品.
  • 从前往后统计物品,即遍历赋值 \(1\) .
  • 记录上一个和当前物品种类相同的物品在哪里,假如我们统计了当前物品,就把上一个相同种类的物品置 \(0\) .
  • 区间 \([i,j]\) 内的物品种类数即为 \(sum(i,j)\).

但是这样做有一个问题,假如上一个相同种类的物品在区间内,但是当前这一个不在. 那么我们统计时,这个物品种类已经置 \(0\) , 就会出现错误.

离线

综上,我们现在需要解决一个问题:

统计当前区间时,这个区间内的物品既不能未统计,也不能已置 \(0\) .

我们可以这样解决:

  • 记录每个区间的右端点,并从小到大排序.
  • 取出一个最小的右端点,若当前对物品的遍历到达了这个右端点,那么马上算出答案并存储.
  • 重复上述步骤.

这样一来,因为置 \(0\) 影响到的区间已经被事先算出来了,所以丝毫没有受到影响.

事实上,我们用到了一种算法思想即离线算法:先存储所有问题,待全部询问结束后再全部输出.

细节处理

1. 关于区间遍历

我们可以开一个变量 \(now\) 作为指针来记录当前遍历到的区间,若答案被算出,则 \(now++\) . 考虑到有右端点重复的区间,因此我们的写法如下:

while(a[now].r==i){
	算出答案并存储;
	now++;
}

2. 关于答案存储

我们可以给区间开一个结构体,这个结构体里除了有区间的左右端点之外,还有一个区间的询问顺序编号. 按这个编号,我们可以做到有序输出.

代码实现

注:本代码使用 封装树状数组

BIT t;
int s[1000001],fore[1000001],ans[1000001]; //s为原序列,fore记录了上一个相同种类的序号,ans记录答案
struct area{
	int l,r,id;
	bool operator <(const area &A)const{
		return r<A.r;
	}
}a[1000001];//记录区间
int n,anow=1,m;
int main(){
	cin>>n;
	t.n=n;
	for(int i=1;i<=n;++i){
		cin>>s[i];
	}
	cin>>m;
	for(int i=1;i<=m;++i){
		cin>>a[i].l>>a[i].r;
		a[i].id=i;
	}
	sort(a+1,a+m+1);
	for(int i=1;anow<=m&&i<=n;++i){
		t.change(i,1);
		if(fore[s[i]]){
			t.change(fore[s[i]],-1);
		}
		fore[s[i]]=i;
		while(a[anow].r==i){
			ans[a[anow].id]=t.sum(a[anow].l,a[anow].r);
			anow++;
		}
	}
	for(int i=1;i<=m;++i){
		cout<<ans[i]<<endl;
	}
}
posted @ 2024-02-18 15:16  HaneDaniko  阅读(51)  评论(6编辑  收藏  举报