洛谷题目链接:[Violet] 蒲公英
一道分块好题,调了整整一上午
一句话题意:在线求区间众数
考虑到众数没有可加性,所以一般数据结构是不好维护的,这个时候就要用分块了,分块可以维护一些数据结构无法维护的复杂神秘数据,对于区间众数就可以用分块维护
考虑暴力做法,对于每一个询问,暴力扫描,时间复杂度\(O(n^2)\),显然无法AC此题,故考虑分块
将区间分为T块,对于每个询问,有以下情况
- 查询区间大小小于块长
- 查询区间大小大于块长
对于第一种情况,我们直接暴力扫描区间\((l ,r)\),时间复杂度\(O(T)\);
对于第二种情况,常见的分块思想是将区间内整块预处理,然后暴力扫描左右的散块,所以,我们要预处理对于每个区间,从整块i,到整块j的众数\(p(i,j)\),预处理直接暴力枚举,时间复杂度\(O(nT^2)\)。思考第二种情况的答案可能情况,发现只能来自整块的众数或者散块中的数,感觉是废话,注意这里散块中的数不一定是散块的众数,所以每一个数都需要遍历(笔者就是这里错了,调了半天),这里,我们因为要遍历每一个数出现次数,如果暴力跑与朴素算法无异,所以需要前缀和优化,对于\(s[i][j]\),表示从第1块到第i块,j出现的次数,可以\(O(1)\)求出整块某个数出现次数。
对于最终答案,我们得出:
- 通过p数组得出只在整块出现的众数。
- 暴力统计散块中数出现的次数,加上整块中这个数出现次数,得出众数。
- 比较两个答案,得出最终结果
最后,我们思考时间复杂度如何最优。时间瓶颈来自于\(O(nT^2)\)预处理,根据基本不等式,当\(T=\sqrt[3]{n}\)时时间复杂度最优,为\(O(n^{\frac{2}{3}})\),实际测试下来,\(T=\sqrt{n}\)也可以过,本题解给出代码为\(T=\sqrt{n}\),这和暴力有什么区别。
等等,还有什么没有考虑?再看一眼数据范围,\(a\le1e9\),要离散化。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10,M=500;
int s[M][N];
int p[M][M];
int a[N];
int b[N],num;
int c[N];
int d[N];
int n,m;
int T,cnt;
int cntnum[N];
struct node
{int l,r;
} pos[M];void dis()
{sort(a+1,a+n+1);for(int i=1;i<=n;i++){if(i==1 || a[i]!=a[i-1]) b[++num]=a[i];}
}int query(int x)
{return lower_bound(b+1,b+num+1,x)-b;
}void init()
{for(int i=1;i<=n;i++) c[i]=query(d[i]);//c[i]表示下标为i的原数的离散值T=pow(n,1/2.0);int l=1,r=T;while(r<=n){pos[++cnt].l=l;pos[cnt].r=r;l=r+1;r=l+T-1;}if(l<=n){pos[++cnt].l=l;pos[cnt].r=n;}//分块for(int i=1;i<=cnt;i++){int l=pos[i].l,r=pos[i].r;for(int j=1;j<=num;j++){int res=0;for(int i=l;i<=r;i++){if(j==c[i]) res++;}s[i][j]=s[i-1][j]+res;}}// 预处理s数组,为前缀和数组int mx=0;for(int i=1;i<=cnt;i++){for(int j=i;j<=cnt;j++){int l=pos[i].l,r=pos[j].r;memset(cntnum,0,sizeof(cntnum)),mx=0;for(int k=l;k<=r;k++){int x=c[k];cntnum[x]++;if(cntnum[x]>mx || (cntnum[x]==mx && x<p[i][j])) mx=cntnum[x],p[i][j]=x;}}}//预处理p数组,注意,p存的是离散化后的值
}pair<int,int> check(int pl,int pr,int l0,int r0)
{int r=pos[pl].l;int mx=0,res=0;//mx为众数个数,res为众数memset(cntnum,0,sizeof(cntnum));for(int i=l0;i<r;i++){int x=c[i];int y=s[pr][x]-s[pl-1][x];cntnum[x]++;if(cntnum[x]+y>mx || (cntnum[x]+y==mx && x<res)) mx=cntnum[x]+y,res=x;}//左侧散块处理int l=pos[pr].r;for(int i=l+1;i<=r0;i++){int x=c[i];int y=s[pr][x]-s[pl-1][x];cntnum[x]++;if(cntnum[x]+y>mx || (cntnum[x]+y==mx && x<res)) mx=cntnum[x]+y,res=x;}//右侧散块处理return {res,mx};
}void solve()
{int ans=0;for(int i=1;i<=m;i++){int l0,r0;scanf("%d%d",&l0,&r0);l0=(l0+ans-1)%n+1;r0=(r0+ans-1)%n+1;if(r0<l0) swap(r0,l0);ans=0;if(r0-l0+1<T){int mx=0;memset(cntnum,0,sizeof(cntnum));for(int i=l0;i<=r0;i++){int x=c[i];cntnum[x]++;if(cntnum[x]>mx || (cntnum[x]==mx && x<ans)) mx=cntnum[x],ans=x;}ans=b[ans];}else{int pl=ceil(l0/(double)T),pr=ceil(r0/(double)T);if(l0!=pos[pl].l) pl++;//最左侧整块if(r0!=pos[pr].r) pr--;//最右侧整块int now=p[pl][pr];pair <int,int> mode;mode=check(pl,pr,l0,r0);int sca=mode.second;//散块众数数量int cop=s[pr][now]-s[pl-1][now];//整块的众数数量if(cop>sca) ans=b[now];else{if(sca>cop) ans=b[mode.first];else ans=min(b[now],b[mode.first]);}}printf("%d\n",ans);}
}
int main(){// freopen("P4168_1.in","r",stdin);// freopen("ans.out","w",stdout);scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]),d[i]=a[i];dis();init();solve();
}
码风冗长,请谅解