HH的项链(树状数组)

由于询问的是区间中贝壳的种类数,所以问询区间中相同种类的贝壳只有一个会起作用

将i位置的贝壳前一次出现的位置记作pre[i],种类为x的贝壳最后一次出现的位置记作f[x],类似于邻接表的nxt和had,利用pre[i]=f[x],f[x]=i来处理pre

对于每个询问[l,r],只有pre[i]<l(l<=i<=r)的贝壳才会被算进答案(这里可以自己想一想为什么是这样)

也就是说所处位置为i的贝壳要想起作用,需要满足两个条件:条件① i必须满足l<=i<=r   条件② pre[i]必须满足pre[i]<l  

我们可以将位置i是第j个询问[l,r]的边界称为位置i和询问j相对应(1<=i<=n,1<=j<=m)

显然一个位置可以对应任意个询问(这个问题会在后面解决),一个询问只能对应两个位置

可以用离线算法,用一个数组vec[i]来记录位置和对应的询问的关系(由于会用到vector,所以我用vec来命名)

一个vec[i]包含两个信息:

①vec[i].num表示位置i所对应的询问所对应的有关数据(这不废话吗 -_-||)(这句话是什么意思呢?假设位置i对应了询问j,而且位置i是询问j的左边界,那么vec[i].num=j*2,如果位置i是询问j的右边界,那么vec[i].num=j*2+1。那这是干什么用的呢?)

②vec[i].to表示位置i所对应的区间[l,r]的左边界l-1,即vec[i].to=l-1(这是干什么用的呢?由于被选中的贝壳必须要满足上面提到的两个条件,所以对于每个询问,只需要用树状数组查询有多少个pre[i]满足0<=pre[i]<=l-1(l<=i<=r)即可)

由于一个位置可以对应任意个询问,所以可以在原来的vec[i]上再加一维vec[i][j],j表示与位置i所对应的多个询问中的第j个询问(0<=j<vec[i].size())

在上面提到:对于每个询问,只需要用树状数组查询有多少个pre[i]满足0<=pre[i]<=l-1(l<=i<=r)即可,所以树状数组的query(x)表示满足0<=pre[i]<=x的i的个数(也可以是满足上述条件的pre[i]的个数,其实都一样的,因为一个i对应一个pre[i]嘛)(这里i的范围可以有两种,对应着两种操作:操作① 一次性把所有的满足0<=pre[i]<=x的i的个数都加进树状数组中,然后再扫描询问计算每个询问的结果,这样的话i的范围是(1<=i<=n)操作② 边扫描每个询问边把pre[i]加进树状数组,这样的话i的范围就是(1<=i<=p)

那么操作①和操作②到底选哪种,就取决于我们需要i满足什么样的范围,满足0<=pre[i]<=l-1(l<=i<=r)的i的数量可以用满足0<=pre[i]<=l-1(1<=i<=r)的i的数量减去满足0<=pre[i]<=l-1(1<=i<=l-1)的i的数量得到

由于上面所需的两个范围(1<=i<=r)(1<=i<=l-1)的两个上界r和l-1都不等于n,所以我们选用操作②

具体实现:用指针i扫描1到n每个位置,边扫边把pre[i]加到树状数组里,如果当前的位置i是询问j的左边界,此时vec[i][j].to为l-1,那么query(vec[i][j].to)的值就是满足0<=pre[i]<=l-1(1<=i<=l-1)的i的数量,这时候vec[i][j].num的作用就体现出来了,因为位置i是询问j的左边界,所以vec[i][j].num的值为j*2,用一个数组ans来记录每个询问的答案信息,对于每个询问j,将ans[j*2]赋值为query(l-1),即ans[vec[i][j].num]=query(vec[i][j].to)。同理,当指针i为询问j的右边界r时,query(vec[i][j].to)的值就是满足0<=pre[i]<=l-1(1<=i<=r)的i的数量,vec[i][j]的值就是j*2+1,进行相同的处理,等到全部扫描完时,枚举每个询问,这时询问j的答案就是ans[j*2+1]-ans[j*2],输出即可。

(由于某种贝壳第一次出现时此位置的pre值为0,且lowbit(0)等于0,在query()函数中会死循环,所以在调用和pre有关的判断时都在原本的值上+1,即可解决,共有两处需要加一,一个是将pre加入树状数组时给pre加一,一个是判断pre是否满足0<=pre<=l-1时给右边界加一(为什么左边界不用加一?这里可以自己想一想))

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-')  f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+c-'0';
		c=getchar();
	}
	return f*x;
}
const int N=1e6+5;
int tre[N],pre[N],f[N],a1,ans[N],n,m,l,r;
struct tree
{
	int num,to;
};
vector <tree> vec[N];
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int y)
{
	for(;x<=n;x+=lowbit(x))  tre[x]+=y;
}
int query(int x)
{
	int sum=0;
	for(;x;x-=lowbit(x))  sum+=tre[x];
        return sum;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a1=read();
		pre[i]=f[a1];
		f[a1]=i;
	}
	m=read();
	for(int i=1;i<=m;i++)
	{
		l=read();  r=read();
		vec[l-1].push_back((tree){i*2,l-1});
		vec[r].push_back((tree){i*2+1,l-1});
	}
	for(int i=1;i<=n;i++)         //这里的i就是上文的指针p
	{
		update(pre[i]+1,1);       //操作②,这里pre要+1
		for(int j=0;j<vec[i].size();j++)
		  ans[vec[i][j].num]=query(vec[i][j].to+1);  //这里用来判断的边界to要+1
	}
	for(int i=1;i<=m;i++)  cout<<ans[i*2+1]-ans[i*2]<<"\n";
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值