BZOJ2724 [Violet 6]蒲公英 分块

原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ2724.html

题目传送门 - BZOJ2724

题意

  求区间最小众数,强制在线。

  $n$ 个数,$m$ 次询问。

  $n\leq 40000,m\leq 50000$

题解

  看完题目:呀这不是莫队裸题吗??

  再看一遍:我去怎么是强制在线!

  然后经过一波思(forever)考(piano),终于会做了。

  首先请你自行证明一个结论:

在询问区间内任取一段子区间,询问区间内的最小众数一定是子区间的最小众数或者出现在询问区间除掉子区间的其他地方。

  于是我们考虑分块,$base=\sqrt{maxn}=200$ 一块。

  我们考虑对于所有 $i,j$ 预处理出第 $i$ 块到第 $j$ 块的区间最小众数。

  我还要预处理出每一个数在前 $i$ 块的出现次数 $\left(i\in \left[1,\left\lfloor\cfrac{n}{base}\right\rfloor\right]\right)$ 。

  于是在询问的时候只要看一看询问区间最大连续块段的最小众数和其他剩余的数就可以了。

  时间复杂度 $\Theta (n \sqrt{n})$ 。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=40005,M=205,base=200;
int n,m,hs,a[N],Ha[N],res[M][M],cnt[M][N],tax[N];
int l,r,L,R;
void HASH(){
	sort(Ha+1,Ha+n+1);
	hs=1;
	for (int i=2;i<=n;i++)
		if (Ha[i]!=Ha[i-1])
			Ha[++hs]=Ha[i];
}
int calc(int x){
	return tax[x]+cnt[r][x]-cnt[l][x];
}
int main(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),Ha[i]=a[i];
	HASH();
	for (int i=1;i<=n;i++)
		a[i]=lower_bound(Ha+1,Ha+hs+1,a[i])-Ha;
	memset(cnt,0,sizeof cnt);
	for (int i=1;i<=base&&i*base<=n;i++)
		for (int j=1;j<=i*base;j++)
			cnt[i][a[j]]++;
	for (int i=1;i<=base&&i*base<=n;i++){
		memset(tax,0,sizeof tax);
		for (int j=i;j<=base&&j*base<=n;j++){
			int &Max=res[i][j];
			Max=res[i][j-1];
			for (int k=(j-1)*base+1,lim=j*base;k<=lim;k++){
				tax[a[k]]++;
				if (tax[a[k]]>tax[Max]||(tax[a[k]]==tax[Max]&&a[k]<Max))
					Max=a[k];
			}
		}
	}
	memset(tax,0,sizeof tax);
	int ans=0;
	while (m--){
		scanf("%d%d",&L,&R);
		L=(L+Ha[ans]-1)%n+1,R=(R+Ha[ans]-1)%n+1;
		if (L>R)
			swap(L,R);
		ans=0;
		if (R-L+1<=base*2){
			for (int i=L;i<=R;i++)
				tax[a[i]]++;
			for (int i=L;i<=R;i++)
				if (tax[a[i]]>tax[ans]||(tax[a[i]]==tax[ans]&&a[i]<ans))ans=a[i];
			for (int i=L;i<=R;i++)
				tax[a[i]]--;
			printf("%d\n",Ha[ans]);
			continue;
		}
		l=(L-1)/base+1,r=R/base;
		ans=res[l+1][r];
		for (int i=L;i<=l*base;i++)
			tax[a[i]]++;
		for (int i=r*base+1;i<=R;i++)
			tax[a[i]]++;
		for (int i=L;i<=l*base;i++)
			if (calc(a[i])>calc(ans)||(calc(a[i])==calc(ans)&&a[i]<ans))ans=a[i];
		for (int i=r*base+1;i<=R;i++)
			if (calc(a[i])>calc(ans)||(calc(a[i])==calc(ans)&&a[i]<ans))ans=a[i];
		for (int i=L;i<=l*base;i++)
			tax[a[i]]--;
		for (int i=r*base+1;i<=R;i++)
			tax[a[i]]--;
		printf("%d\n",Ha[ans]);
	}
	return 0;
}

  

 

转载于:https://www.cnblogs.com/zhouzhendong/p/BZOJ2724.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值