洛谷P1972 [SDOI2009]HH的项链 树状数组、线段树、主席树、莫队四解

初学莫队找题练手,于是,我死了。 —2019.7.16晚
洛谷这都是什么神奇分类啊莫队是最慢的一个解法,常数优化是膜法!!!!!!
上题:在这里插入图片描述
树状数组与线段树的解法相似,都是离线做法,将所有询问区间记录后以右端从小到大排序,再依次解答。
题目主要解法是:在区间[l,r]中统计出现的数字的类数,我们只关心在区间内每一个数字最后一次出现的位置,如:1 3 4 2 3 1 5 7 3 。对于区间[2,5]我们只需记录4,2,和第二个3的位置,之前有多少个3都和答案无关。所以我们只需要不断更新每个数字最后出现的位置即可,新出现就该位置加1,被顶替就原位置减一,用树状数组的前缀和或线段树的区间相合性来统计答案。

树状数组:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e6+10;
int n,m,ans[maxn],inp[maxn],fst[maxn],c[maxn];
struct query{
	int l,r,num;
}infor[maxn];
bool comp(query a,query b){
	return a.r<b.r;
}
int lowbit(int x){
	return x&(-x);
}
void add(int x,int num){
	for(int i=x;i<=n;i+=lowbit(i)){
		c[i]+=num;
	}
}
int getsum(int x){
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i)){
		sum+=c[i];
	}
	return sum;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&inp[i]);
	}
	scanf("%d",&m);
	for(int i=0;i<m;i++){
		scanf("%d%d",&infor[i].l,&infor[i].r);
		infor[i].num=i+1;
	}
	sort(infor,infor+m,comp);
	int st=1;
	for(int i=0;i<m;i++){
		for(;st<=infor[i].r;st++){
			if(fst[inp[st]]){
				add(fst[inp[st]],-1);
			}
			add(st,1);
			fst[inp[st]]=st;
		}
		ans[infor[i].num]=getsum(infor[i].r)-getsum(infor[i].l-1);
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);
	} 
	return 0;
}

在这里插入图片描述
线段树:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e6+10;
int n,m,inp[maxn],ans[maxn],fst[maxn];
struct node{
	int l,r,num;
}infor[2*maxn];
void getsum(int p){
	infor[p].num=infor[p<<1].num+infor[p<<1|1].num;
}
void add(int p,int l,int r,int k){
	if(l==infor[p].l&&r==infor[p].r){
		infor[p].num+=k;
		return;
	}
	int i=p<<1;
	if(l<=infor[i].r){
		if(r<=infor[i].r){
			add(i,l,r,k);
		}
		else{
			add(i,l,infor[i].r,k);
		}
	}
	i++;
	if(r>=infor[i].l){
		if(l>=infor[i].l){
			add(i,l,r,k);
		}
		else{
			add(i,infor[i].l,r,k);
		}
	}
	getsum(p);
}
void build_tree(int p,int l,int r){
	infor[p].l=l;
	infor[p].r=r;
	if(l==r){
		return;
	}
	int mid=(l+r)>>1;
	build_tree(p<<1,l,mid);
	build_tree(p<<1|1,mid+1,r);
}
int getans(int p,int l,int r){
	if(l==infor[p].l&&r==infor[p].r){
		return infor[p].num;
	}
	if(l<=infor[p<<1].r&&r<=infor[p<<1].r){
		return getans(p<<1,l,r);
	}
	else if(l<=infor[p<<1].r&&r>=infor[p<<1|1].l){
		return getans(p<<1,l,infor[p<<1].r)+getans(p<<1|1,infor[p<<1|1].l,r);
	}
	else{
		return getans(p<<1|1,l,r);
	}
}
struct query{
	int l,r,num;	
}ask[maxn];
bool comp(query a,query b){
	return a.r<b.r;
}
int main(){
	scanf("%d",&n);
	build_tree(1,1,n);
	for(int i=1;i<=n;i++){
		scanf("%d",&inp[i]);
	}
	scanf("%d",&m);
	for(int i=0;i<m;i++){
		scanf("%d%d",&ask[i].l,&ask[i].r);
		ask[i].num=i;
	}
	sort(ask,ask+m,comp);
	int st=1;
	for(int i=0;i<m;i++){
		for(;st<=ask[i].r;st++){
			if(fst[inp[st]]){
				add(1,fst[inp[st]],fst[inp[st]],-1);
			}
			fst[inp[st]]=st;
			add(1,st,st,1);
		}
		ans[ask[i].num]=getans(1,ask[i].l,ask[i].r);
	}
	for(int i=0;i<m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}

在这里插入图片描述
主席树我写的是在线的解法,因为主席树保存了所有状态下的数据,所以直接O(logn)查询就行了。在主席树解法中,我们要保存的是每个数值它的下一个出现的位置,并给此处加1。查询时查有多少个大于区间右端(下一次出现在区间外,即原值是此区间内最后一次出现)的值就行了。
主席树:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e6+10;
const int mx = 5e5+10;
int n,m,inp[mx],nx[maxn],last[maxn],root[mx],cnt,sum;
struct node{
	int lson,rson,num;
}infor[30*mx];
void update(int &p,int old,int l,int r,int tag){
	p=++cnt;
	infor[p]=infor[old];
	infor[p].num++;
	if(l==r){
		return;
	}
	int mid=(l+r)>>1;
	if(tag<=mid){
		update(infor[p].lson,infor[p].lson,l,mid,tag);
	}
	else{
		update(infor[p].rson,infor[p].rson,mid+1,r,tag);
	}
}
int query(int l,int r,int ql,int qr,int tag){
	if(l==r){
		return infor[qr].num-infor[ql].num;
	}
	int mid=(l+r)>>1;
	if(tag<=mid){
		return query(l,mid,infor[ql].lson,infor[qr].lson,tag)+infor[infor[qr].rson].num-infor[infor[ql].rson].num;
	}
	else{
		return query(mid+1,r,infor[ql].rson,infor[qr].rson,tag);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&inp[i]);
		if(last[inp[i]]){
			nx[last[inp[i]]]=i;
		}
		last[inp[i]]=i;
	}
	for(int i=1;i<=n;i++){
		if(!nx[i]){
			nx[i]=n+1;
		}
	}
	for(int i=1;i<=n;i++){
		update(root[i],root[i-1],1,n+1,nx[i]);
	}
	scanf("%d",&m);
	for(int i=0;i<m;i++){
		sum=0;
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",query(1,n+1,root[l-1],root[r],r+1));
	}
	return 0;
} 

在这里插入图片描述
最后莫队解法。莫队就是一个优化O(n n \sqrt{n} n )的大暴力,套板子就行了。不过最后两个点要奇偶排序加上常数优化再加吸氧才能过。(注释掉的是80分的解法)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 1e6+10;
int ans[maxn],inp[maxn],n,m,block,tmp,cnt[maxn];
struct query{
	int l,r,num,bl;
}infor[maxn];
//void Add(int pos){
//	if(!cnt[inp[pos]]){
//		tmp++;
//	}
//	cnt[inp[pos]]++;
//}
//void del(int pos){
//	if(cnt[inp[pos]]==1){
//		tmp--;
//	}
//	cnt[inp[pos]]--;
//}
//bool comp(query a,query b){
//	return a.bl==b.bl?a.r<b.r:a.bl<b.bl;
//}
bool comp(query a, query b) {
    return (a.bl^b.bl)?a.bl<b.bl:(a.bl&1)?a.r<b.r:a.r>b.r;
}
int main(){
	scanf("%d",&n);
	block = sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&inp[i]);
	}
	scanf("%d",&m);
	for(int i=0;i<m;i++){
		scanf("%d%d",&infor[i].l,&infor[i].r);
		infor[i].num=i+1;
		infor[i].bl=infor[i].l/block;
	}
	sort(infor,infor+m,comp);
	int l=1,r=0;
	for(int i=0;i<m;i++){
		int ql=infor[i].l,qr=infor[i].r;
//		while(l<ql){
//			del(l++);
//		}
//		while(l>ql){
//			Add(--l);
//		}
//		while(r<qr){
//			Add(++r);
//		} 
//		while(r>qr){
//			del(r--);
//		}
        while(l < ql) tmp -= !--cnt[inp[l++]];
        while(l > ql) tmp += !cnt[inp[--l]]++;
        while(r < qr) tmp += !cnt[inp[++r]]++;
        while(r > qr) tmp -= !--cnt[inp[r--]];
		ans[infor[i].num]=tmp;
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);
	} 
	return 0;
}

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值