P1972 [SDOI2009]HH的项链(树状数组,离线,贪心)

P1972 [SDOI2009]HH的项链

题目描述

给出一列数,试求在指定范围内有多少个不同的数. 数列的长度不大于 1000000 1000000 1000000,每个数的大小都不超过 1000000 1000000 1000000.

题目分析

我们不妨把问题简化,想一想只有一组询问的时候应该怎么做.

考虑最暴力的做法,

由数列从左到右,如果发现尚未标记的颜色,标记,颜色数加一.

我们发现,还有一种等价的做法,发现标记过的颜色后,颜色数先减一,取消该颜色的标记,然后再标记,颜色数加一.

可能有人会说这不就是没事找事干吗?实际上,以上两种操作方式是不一样的,后一种实际上告诉我们,不管一种颜色出现了多少次,数列的颜色数都只需要在最后一次出现时统计即可.

这对于我们做题有什么帮助呢?

我们不妨假设输入的查询中,查询的右界是依次递增的,那么很明显,我们可以由前一次查询的右界拓展到现在这一次查询的右界,那么我们就在拓展的过程中维护颜色数即可.

也就是说,最后一个位置前的该颜色完全没有意义,那么它完全可以被后面的该颜色替代.

得到这个结论后,我们考虑怎么使用它.

发现这个东西可以用树状数组来维护. 也就是说,我们维护树状数组,求区间时就统计区间前缀和.

每出现一个新的元素就 a d d ( 1 ) add(1) add(1),而取消前一个相同颜色对前缀和的影响,可以 a d d ( − 1 ) add(-1) add(1).

最后区间前缀和作差,就是答案.

不要忘了之前的结论是在排序的情况下才有的!区间一定按右端点排序!

程序实现

#include<bits/stdc++.h>
#define maxn 1000010
using namespace std;
struct number{
	int l,r,rank;
}num[maxn];
int n,m,tree[maxn],last_num[maxn],a[maxn],ans[maxn];
bool vis[maxn];
int lowbit(int x){return x&(-x);}
void add(int x,int k){
	for(int i=x;i<=n;i+=lowbit(i))tree[i]+=k;
}
int query(int x){
	int ret=0;
	for(int i=x;i;i-=lowbit(i))ret+=tree[i];
	return ret;
}
bool cmp(number x,number y){
	return x.r <y.r ;
}//右端点小的在左边
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&num[i].l ,&num[i].r );
		num[i].rank =i;
	}
	int next=1;
	sort(num+1,num+m+1,cmp);
	for(int i=1;i<=m;i++){
		for(int j=next;j<=num[i].r ;j++){
			add(j,1);
			if(vis[a[j]])add(last_num[a[j]],-1); //这个颜色的前一个位置,要其对前缀和的消除影响
			vis[a[j]]=true;
			last_num[a[j]]=j;
		}
		next=num[i].r ;
		ans[num[i].rank ]=query(num[i].r )-query(num[i].l -1);//前缀和作差
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);//别忘了按输入序输出
	return 0;
}

题后总结

对于这种题目,还是不太能想到正解的方法.

想得到前缀和,但是想不到有关“最后一个才对整体有影响”之类的, a d d ( − 1 ) add(-1) add(1)的操作也很妙.

很多题都有类似的贪心呢,关于影响的转移之类的.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值