【BZOJ 3809】Gty的二逼妹子序列

题目描述

Autumn 和 Bakser 又在研究 Gty 的妹子序列了!但他们遇到了一个难题。
对于一段妹子们,他们想让你帮忙求出这之内美丽度 ∈ [ a , b ] \in [a,b] [a,b] 的妹子的美丽度的种类数。
为了方便,我们规定妹子们的美丽度全都在 [ 1 , n ] [1,n] [1,n] 中。
给定一个长度为 n ( 1 ≤ n ≤ 100000 ) n(1\le n\le 100000) n(1n100000) 的正整数序列 s ( 1 ≤ s i ≤ n ) s(1\le s_i\le n) s(1sin),对于 m ( 1 ≤ m ≤ 1000000 ) m(1\le m\le 1000000) m(1m1000000) 次询问 l , r , a , b l,r,a,b l,r,a,b,每次输出 $s_l\dots s_r中,权值 ∈ [ a , b ] \in [a,b] [a,b] 的权值的种类数。

算法分析

莫队算法+分块。

一个显然的思路是莫队之后每次区间变化都使用线段树来维护,然而这样的时间复杂度为 O ( n n l o g 2 n ) O(n\sqrt{n}log_2n) O(nn log2n),会超时。

我们发现对于一个询问,我们处理部分的时间复杂度为均摊 O ( n l o g 2 n ) O(\sqrt{n}log_2n) O(n log2n),而计算答案部分的复杂度近为 l o g 2 n log_2n log2n,需要减少处理部分的复杂度,可适当增加计算答案部分的复杂度。

对权值分块,每个权值维护目前区间中其出现次数 c n t cnt cnt,对每块维护块中 c n t cnt cnt 不为 0 0 0 的个数,这样,区间单位变化时的复杂度就是 O ( 1 ) O(1) O(1),区间变化部分的总复杂度为 O ( n ) O(\sqrt{n}) O(n ),每次查询部分小块暴力,大块直接查询,时间复杂度为 O ( n ) O(\sqrt{n}) O(n ),总时间复杂度为 O ( n n ) O(n\sqrt{n}) O(nn )

注意分块时要判断左右端点是否在同一个块内,预处理每个块的左右边界以方便处理时要记得初始化。

代码实现

#include <cstdio>
#include <cmath>
#include <algorithm>
#define cmin(x,y) x=std::min(x,y)
#define cmax(x,y) x=std::max(x,y)
const int maxn=100005;
const int maxm=1000005;
struct ask {int id,l,r,a,b;} qs[maxm];
int sz,bl[maxn],le[maxn],ri[maxn];
inline bool cmp(const ask &x,const ask &y) {return bl[x.l]^bl[y.l]?x.l<y.l:bl[x.l]&1?x.r<y.r:x.r>y.r;}
int s[maxn],num[maxn],cnt[maxn],ans[maxm];
int main() {
	int n,m;scanf("%d%d",&n,&m);
	for(register int i=1;i<=n;++i) scanf("%d",&s[i]);
	for(register int i=0;i<m;++i) {qs[i].id=i;scanf("%d%d%d%d",&qs[i].l,&qs[i].r,&qs[i].a,&qs[i].b);}
	sz=n/sqrt(m*2/3);for(register int i=1;i<=n;++i) {le[i]=n;ri[i]=0;}
	for(register int i=1;i<=n;++i) {bl[i]=(i-1)/sz+1;cmin(le[bl[i]],i);cmax(ri[bl[i]],i);}
	std::sort(qs,qs+m,cmp);
	for(register int i=0,l=1,r=0;i<m;++i) {
		while(l<qs[i].l) {--cnt[s[l]];num[bl[s[l]]]-=(!cnt[s[l]]);++l;}
		while(l>qs[i].l) {--l;num[bl[s[l]]]+=(!cnt[s[l]]);++cnt[s[l]];}
		while(r<qs[i].r) {++r;num[bl[s[r]]]+=(!cnt[s[r]]);++cnt[s[r]];}
		while(r>qs[i].r) {--cnt[s[r]];num[bl[s[r]]]-=(!cnt[s[r]]);--r;}
		int &res=ans[qs[i].id];int a=qs[i].a,b=qs[i].b;
		if(bl[a]^bl[b]) {
			for(register int j=a;j<=ri[bl[a]];++j) if(cnt[j]) ++res;
			for(register int j=bl[a]+1;j<=bl[b]-1;++j) res+=num[j];
			for(register int j=le[bl[b]];j<=b;++j) if(cnt[j]) ++res;
		}
		else for(register int j=a;j<=b;++j) if(cnt[j]) ++res;
	}
	for(register int i=0;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、付费专栏及课程。

余额充值