【BZOJ2724】蒲公英 题解(分块+区间众数)
题目大意:给定一段长度为$n$的序列和$m$次询问,每次询问区间$[l,r]$内的最小的众数。$n\leq 40000,a_i\leq 10^9$
-----------------------------
因为$a_i\leq 10^9$,显然不能开那么大的数组。所以要离散化。对于离散化后的数组,我们维护两个值$sum[i][j]$和$p[i][j]$。$sum[i][j]$表示前$i$个块中$j$出现的次数,这个$O(n \sqrt n)$暴力枚举就好。$p[i][j]$表示块$i$到$j$的众数,这个只需开一个桶维护,然后$O(\sqrt n \sqrt n \sqrt n)$暴力枚举就好。
对于查询,我们仍然暴力把区间$[l,r]$中边边角角的部分暴力求出来,对于整块我们用之前维护的$sum$和$p$即可。
其实就是一个大模拟……细节有点小多。不会真的有人连敲代码都不会吧
代码(有些细节的地方有注释):
#include<bits/stdc++.h>
using namespace std; const int maxn=40005; int n,m,block,tot,sum[205][maxn]; int last,pre[maxn],tmpnum[maxn],bucket[maxn],vis[maxn]; struct Node { int id,val,se;//id编号,val值,se离散化后的值 }a[40005]; struct node { int num,s; }p[205][205]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } bool cmp1(Node a,Node b){return a.val<b.val;}//离散化排序 bool cmp2(Node a,Node b){return a.id<b.id;} int getpos(int x)//不用维护每个区间的左右端点了,一个函数搞定 { int pos=x/block; if (x%block) pos++; return pos; } inline void build() { for (int i=1;i<=tot;i++) { memset(bucket,0,sizeof(bucket));node tmp;//注意清空桶 tmp.s=tmp.num=0; for (int j=i;j<=tot;j++) { for (int k=(j-1)*block+1;k<=min(n,j*block);k++)//预处理 { bucket[a[k].se]++; if (bucket[a[k].se]>tmp.s) { tmp.s=bucket[a[k].se]; tmp.num=a[k].se; } else if (bucket[a[k].se]==tmp.s) tmp.num=min(tmp.num,a[k].se); } p[i][j]=tmp; } } for (int i=1;i<=tot;i++){//预处理 for (int j=1;j<=n;j++) sum[i][a[j].se]=sum[i-1][a[j].se]; for (int j=(i-1)*block+1;j<=min(n,i*block);j++) sum[i][a[j].se]++; } } inline void query(int l,int r) { int posl=getpos(l),posr=getpos(r); if (posr-posl<=2)//如果区间范围较小直接暴力枚举即可 { int ans=0; for (int i=l;i<=r;i++) tmpnum[a[i].se]=0;//开一个桶,注意清空 for (int i=l;i<=r;i++){ tmpnum[a[i].se]++; if (tmpnum[a[i].se]>tmpnum[ans]) ans=a[i].se; else if (tmpnum[a[i].se]==tmpnum[ans]) ans=min(ans,a[i].se); } printf("%d\n",last=pre[ans]); return; } int ans=p[posl+1][posr-1].num,maxsum=0,maxnum;//用预处理的p数组维护ans vis[ans]=0;tmpnum[ans]=0; ////////////暴力把边边角角统计出来////////////// for (int i=l;i<=min(n,posl*block);i++) tmpnum[a[i].se]=0,vis[a[i].se]=0; for (int i=(posr-1)*block+1;i<=r;i++) tmpnum[a[i].se]=0,vis[a[i].se]=0; for (int i=l;i<=min(n,posl*block);i++) tmpnum[a[i].se]++; for (int i=(posr-1)*block+1;i<=r;i++) tmpnum[a[i].se]++; for (int i=l;i<=min(n,posl*block);i++){ if (vis[a[i].se]) continue; vis[a[i].se]=1; int summ=tmpnum[a[i].se]+sum[posr-1][a[i].se]-sum[posl][a[i].se];//个数 if (summ>maxsum) maxsum=summ,maxnum=a[i].se; else if (maxsum==summ) maxnum=min(maxnum,a[i].se); } for (int i=(posr-1)*block+1;i<=r;i++){ if (vis[a[i].se]) continue; vis[a[i].se]=1; int summ=tmpnum[a[i].se]+sum[posr-1][a[i].se]-sum[posl][a[i].se];//个数 if (summ>maxsum) maxsum=summ,maxnum=a[i].se; else if (maxsum==summ) maxnum=min(maxnum,a[i].se); } /////////////////////////////////////////////// if (maxsum>tmpnum[ans]+p[posl+1][posr-1].s) ans=maxnum; else if (maxsum==tmpnum[ans]+p[posl+1][posr-1].s) ans=min(ans,maxnum); printf("%d\n",last=pre[ans]); } int main() { n=read(),m=read();block=sqrt(n); tot=(n+block-1)/block; for (int i=1;i<=n;i++) a[i].val=read(),a[i].id=i; sort(a+1,a+n+1,cmp1);a[0].val=-1; for (int i=1;i<=n;i++)//离散化 { a[i].se=a[i-1].se; if (a[i].val!=a[i-1].val) a[i].se++; pre[a[i].se]=a[i].val; } sort(a+1,a+n+1,cmp2); build(); for (int i=1;i<=m;i++) { int l=read(),r=read(); l=(l+last-1)%n+1; r=(r+last-1)%n+1; if (l>r) swap(l,r); query(l,r); } return 0; }