您的位置:首页 > 其它

bzoj 2724【蒲公英】

2019-01-15 07:12 661 查看

  啊,题目比较冗余,直接一句题意:求任意区间众数。

  俗话说得好,面向数据编程。这道题在洛谷和bzoj上的数据都是比较友好的,n<=40000,m<=50000。那不是挺容易吗!

  首先确定一点,本题强制在线,所以就不要去想一些乱七八糟的离线算法了。区间众数这种不满足区间加减性质的,又不能写线段树合并这类数据结构,很显然是暴力分块啊。我们先假设n这个序列平均分为T个块,然后对于每一个块(这个块也可以是任意一段连续块组成的)存一个块内众数以及每个数在块内出现的次数,显然复杂度是NT^2的,并且空间也是一样。然后考虑任意一个区间l,r我们怎么求区间众数。首先我们找到l,r包含的一段最长连续块,这个块内的众数我们直接知道了,然后左边和右边会残留有一些不在块内的多余部分,其实区间l,r的众数只有可能是当前块内众数或者这些多余的数中的某一个,所以我们只需要把多余的数依次扫一遍,累加到块内每个数出现的次数上,看能否更新众数。在求得答案之后,再重新扫一遍把修改的次数改回来。这样的话,因为左右两边多余的数不可能超过N/T个,那么查询的复杂度是N/T*M的,然后总的复杂度就是NT^2+N/T*M,然后我们利用一点均值不等式的思路,让两个尽量相等达到最优复杂度,求得T≈M^(1/3),整个算法复杂度就是O(N^(5/3))级别的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=41000;
const int T=1600;
int t,n,m,lan,pos,ans,maxx,S,p,q,a[41000],b[41000],c[40],sum[1600][41000],most[1600],temp[41000],maxn[1600];
void discrete(){
sort(b+1,b+n+1);
int sz=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+sz+1,a[i])-b;
}
int ask(int l,int r){
l=(l+lan-1)%n+1;r=(r+lan-1)%n+1;
if(l>r) swap(l,r);
p=l/S,q=r/S;
if(l%S==1) p++;
else if(l%S) p+=2;
else p++;
if(q-p<0){
maxx=0,ans=0;
for(int i=l;i<=r;++i){
temp[a[i]]++;
if(temp[a[i]]>maxx) maxx=temp[a[i]],ans=a[i];
if(temp[a[i]]==maxx&&b[a[i]]<b[ans]) ans=a[i];
}
for(int i=l;i<=r;++i) temp[a[i]]--;
return b[ans];
}else{
pos=c[p-1]+q-p+1;ans=most[pos],maxx=maxn[pos];
for(int i=l;i<=S*(p-1);++i){
sum[pos][a[i]]++;
if(sum[pos][a[i]]>maxx) maxx=sum[pos][a[i]],ans=a[i];
if(sum[pos][a[i]]==maxx&&b[ans]>b[a[i]]) ans=a[i];
}
for(int i=S*q+1;i<=r;++i){
sum[pos][a[i]]++;
if(sum[pos][a[i]]>maxx) maxx=sum[pos][a[i]],ans=a[i];
if(sum[pos][a[i]]==maxx&&b[ans]>b[a[i]]) ans=a[i];
}
for(int i=l;i<=S*(p-1);++i) sum[pos][a[i]]--;
for(int i=S*q+1;i<=r;++i) sum[pos][a[i]]--;
return b[ans];
}
}
int main(){
//freopen("h.in","r",stdin);
//freopen("h.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=a[i];
discrete();
t=pow(1.0*n,1.0/3.0);S=n/t;
for(int i=1;i<=t;++i) c[i]=c[i-1],c[i]+=t-i+1;
for(int i=1;i<=t;++i){
for(int j=1;j<=t-i+1;++j){
pos=c[i-1]+j;maxx=0,ans=0;
for(int k=(i-1)*S+1;k<=S*(i+j-1);++k){
sum[pos][a[k]]++;
if(sum[pos][a[k]]>maxx) maxx=sum[pos][a[k]],ans=a[k];
if(sum[pos][a[k]]==maxx&&b[a[k]]<b[ans]) ans=a[k];
}
most[pos]=ans,maxn[pos]=maxx;
}
}
for(int i=1;i<=m;++i){
int l,r;scanf("%d%d",&l,&r);
printf("%d\n",lan=ask(l,r));
}
return 0;
}

  但是比较鬼畜的是,这道题在loj模板里面数据范围加强了。长度为n的序列,询问次数为n,n<=1e5。这个复杂度,好像上面那种算法不管怎么调都过不去啊。。我们考虑换一种思路,还和上面一样,我们每一个块需要处理出区间众数,然后对于边缘的每个数,我们考虑怎么快速查询它在块内出现的次数。我们可以对每个数值建立一个vector,存储的是这个数值所在的所有位置,那么我们此时可以通过两次二分求出这个数在当前块内出现的次数。复杂度应该是NT+N*N/T*log N,取T=sqrt(NlogN),整个复杂度为O(N*sqrt(N logN)),空间复杂度为N logN。足以通过这道题。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: