P1972 [SDOI2009]HH的项链

原题链接
考察:树状数组
思路:
  本蒟蒻是完全想不到怎么写....参考大佬题解.
  求种类数,可以处理的方法一个是针对询问实时加入数.但是这样一定会TLE,然后不好求重复的数字数.
  参考大佬的题解,正解做法是i从1~n实时加入数,到询问的右端点求ans.这里与求逆序对的方法不同的是tr[i]表示下标为i时有多少个有效的数字.即add(i,1)表示下标为1处有效数字+1.
  然后就是重复数字如何求.对于每一个询问,我们优先处理右端点靠前的.每一个重复数字,我们只在最右端add(i,1).之前的add(pre,-1).看代码吧,这题解思路基本参考大佬的.
  为什么以右端点排序呢?如果左端点靠前的区间太长,后面的区间又较短,导致重复数字的有效位被更新到后面导致答案错误.
如果以左端点排序就需要从后往前处理,否则时间复杂度降不下来

#include <iostream> 
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N = 1000010;
int a[N],n,m,tr[N],c[N],ans[N];
struct Query{
	int l,r,id,ans;
	bool operator<(Query q){
		if(this->r!=q.r) return this->r<q.r;
		return this->l<q.l;
	}
}q[N];
int lowbit(int x)
{
	return x&-x;
}
void add(int k,int x)
{
	for(int i=k;i<=n;i+=lowbit(i)) tr[i]+=x;
}
int ask(int x)
{
	int res = 0;
	for(int i=x;i;i-=lowbit(i)) res+=tr[i];
	return res;
}
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",&q[i].l,&q[i].r);
		q[i].id = i;
	}
	sort(q+1,q+m+1);
	for(int i=1,j=1;i<=n;i++)
	{
		if(c[a[i]]) add(c[a[i]],-1);
		add(i,1);
		c[a[i]] = i;
		while(q[j].r==i)
		{
			q[j].ans = ask(q[j].r)-ask(q[j].l-1);
			j++;
		}
	}
	for(int i=1;i<=m;i++) ans[q[i].id] = q[i].ans;
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}
posted @ 2021-05-15 23:36  acmloser  阅读(52)  评论(0编辑  收藏  举报