[SDOI2009]HH的项链 解题报告

一、这道题。。据说有大神考场上的时候搞了一坨线段树实现了在线。。

二、离线是早就想到了,但是一开始我是按左端点扫的。。导致。。相当麻烦,还做了个前缀和什么的。

三、其实如果按右端点扫,就很简单了!

对于许多相同的数,我们完全可以只保存当前它们最右边那个,然后被保存的数被置1,没保存的置0,于是就成了简单的区间求和了。

四、当然还有一种比较难理解的差分做法,说起来比较麻烦。。也比较蛋疼。。绕两下就绕晕了。但是不知道怎么回事COGS上的大神们都是这么做的,这里就不推荐了。

扫描左端点:

#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define root 1,1,N
#define lson node<<1,l,(l+r)>>1
#define rson node<<1|1,((l+r)>>1)+1,r
char * ptr=(char *)malloc(10000000);
int pp[1000001],tree[200000],ans[200000];
struct S{
	int l,r,i;
	inline bool operator < (S a)const{
		return l<a.l;
	}
}q[200001];
inline void in(int &x){
	while(*ptr<'0'||*ptr>'9')++ptr;
	x=0;
	while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0';
}
inline void add(int node,int l,int r,int a,int x){
	if(l==r)tree[node]+=x;
	else{
		if(((l+r)>>1)<a)add(rson,a,x);
		else add(lson,a,x);
		tree[node]=tree[node<<1]+tree[node<<1|1];
	}
}
inline int query(int node,int l,int r,int a,int b){
	if(a<=l&&r<=b)return tree[node];
	if(a>r||b<l)return 0;
	return query(lson,a,b)+query(rson,a,b);
}
int main(){
	freopen("diff.in","r",stdin);
	freopen("diff.out","w",stdout);
	fread(ptr,1,10000000,stdin);
	int a[50001],s[50001],tot=0,p[50001]={0},next[50001],j=0,i,N,M;
	in(N);
	s[0]=0;
	for(i=1;i<=N;++i){
		in(a[i]);
		if(!pp[a[i]]){
			s[i]=s[i-1]+1;
			pp[a[i]]=1;
		}
		else s[i]=s[i-1];
	}
	for(i=0;i<1000001;++i)
		if(pp[i])
			pp[i]=tot++;
	for(i=N;i;--i){
		a[i]=pp[a[i]];
		next[i]=p[a[i]];
		p[a[i]]=i;
	}
	in(M);
	for(i=0;i<M;++i)in(q[i].l),in(q[i].r),q[i].i=i;
	sort(q,q+M);
	q[M].l=-1;
	for(i=1;i<=N;++i){
		for(;q[j].l==i;++j)ans[q[j].i]=s[q[j].r]-s[i-1]+query(root,i,q[j].r);
		if(p[a[i]]!=i)add(root,i,-1);
		if(next[i])add(root,next[i],1);
	}
	for(i=0;i<M;++i)printf("%d\n",ans[i]);
}

扫描右端点:

#include<iostream>
using namespace std;
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
char * ptr=(char *)malloc(3000000);
struct QS{
	int l,r,i;
	inline bool operator < (QS a)const{
		return r<a.r;
	}
}Q[200001];
int a[50001],N,pred[1000001],bit[50001],ans[200000];
char buf[2000000];
inline void in(int &x){
	while(*ptr<'0'||*ptr>'9')++ptr;
	x=0;
	while(*ptr>47&&*ptr<58)x=x*10+*ptr++-'0';
}
int main(){
	freopen("diff.in","r",stdin);
	freopen("diff.out","w",stdout);
	fread(ptr,1,3000000,stdin);
	in(N);
	int i,j,now,M,x,get;
	for(i=1,++N;i<N;++i)in(a[i]);
	in(M);
	for(i=0;i<M;++i)in(Q[i].l),in(Q[i].r),Q[i].i=i;
	sort(Q,Q+M);
	Q[M].r=-1;
	for(i=1,j=0;i<N&&j<M;++i){
		for(x=i;x<N;x+=x&-x)++bit[x];
		if(pred[a[i]])
			for(x=pred[a[i]];x<N;x+=x&-x)
				--bit[x];
		now=0;
		for(x=i;x;x-=x&-x)now+=bit[x];
		for(;Q[j].r==i;++j){
			get=0;
			for(x=Q[j].l-1;x;x-=x&-x)get+=bit[x];
			ans[Q[j].i]=now-get;
		}
		pred[a[i]]=i;
	}
	char tmp[10],* ptr1=buf;
	int ptr2=0;
	for(i=0;i<M;++i){
		for(;ans[i];ans[i]/=10)tmp[++ptr2]=ans[i]%10+'0';
		while(ptr2)*ptr1++=tmp[ptr2--];
		*ptr1++='\n';
	}
	fwrite(buf,1,ptr1-buf,stdout);
}


总结:

①离线处理询问区间时不仅可以扫左端点,也可以扫右端点。

②优化输出时开一个辅助小数组以做到逆序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值