洛谷 P1972 [SDOI2009]HH的项链 题解

可以发现,这道题目在线很难做(没准真的有某些神犇做得出来呢。。),于是(本蒟蒻)只能采用离线的做法,具体做法是这样的:首先,将所有的询问按右端点排个序,左端点不管,然后按顺序做每一个询问,那具体怎么做呢,见下:

比如当前我们做到某个询问,它的左端点为l,右端点为r,那么我们需要将项链中前r个贝壳给处理完,因为r是升序的,所以这个处理其实只需要线性时间,那么当前我们应该处理1~r区间内的贝壳,先定义一个s[i],用法后面会提到,那么怎么处理呢?对于每一种贝壳,我们使所有这种贝壳中出现最晚的——也就是最右边的——那个的s值变成1,其他的都为0,处理完之后,我们就可以直接统计l~r中的s值的和即可,这样子可以保证每种贝壳只会被算一个,那么区间和这个东西我们又可以用树状数组来维护,这样时间复杂度就会很优了,并且其实s数组是不用出现的。那么接下来......上代码!

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n,m,a[500010],last[1000010];//last记录每种颜色最后出现的那个的位置 
struct node{int x,y,z;};
node b[200010];
int tree[500010];//树状数组 
int lowbit(int x){return ((-x)&x);}
bool cmp(node x,node y){return x.y<y.y;}//以右端点为关键字 
void change(int x,int y)//修改 
{
	if(x==0)return;
	for(int i=x;i<=n;i+=lowbit(i))
	tree[i]+=y;
}
int sum(int x)//查询 
{
	int p=0;
	for(int i=x;i>=1;i-=lowbit(i))
	p+=tree[i];
	return p;
}
int ans[200010];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	memset(last,0,sizeof(last));
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	scanf("%d %d",&b[i].x,&b[i].y),b[i].z=i;//b[i].z用来记录询问的编号,便于输出 
	sort(b+1,b+m+1,cmp);//排序 
	int j=0;//记录处理到了哪个贝壳 
	memset(tree,0,sizeof(tree));// 
	for(int i=1;i<=m;i++)
	{
		while(j<b[i].y)// 
		{
			j++;
			change(last[a[j]],-1);//将前面的一个的标记去掉 
			last[a[j]]=j;//记录 
			change(j,1);//重新标记 
		}
		ans[b[i].z]=sum(b[i].y)-sum(b[i].x-1);//统计区间和并记录 
	}
	for(int i=1;i<=m;i++)
	printf("%d\n",ans[i]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/80069477