标签:min sizeof names 二分查找 code stdin target 离散 blank
md调了一个晚上最后发现竟然是空间开小了……明明算出来够的……
讲真其实我以前不太瞧得起分块,觉得这种基于暴力的数据结构一点美感都没有。然而今天做了这道分块的题才发现分块的暴力之美(如果我空间没有开小就更美了)
我们先将整个数组分块,设块的大小为$T$
我们先预处理出所有以块边界为端点的区间的答案,即$ans[L][R]$代表着第$L$块到第$R$块的序列所代表的答案。这个可以$O(n*n/T)$预处理
然后我们先将所有的数给离散化,然后对每一个值都开一个vector,记录这个值在数组中出现的每一个位置。比如数组的下标为1,3,5的位置都是3,那么3的vector记录的就是{1,3,5}
这个有什么用呢?我们设查询的区间为$[l,r]$,然后在这个vector里先二分查找第一个大于等于$l$的数的位置,再二分查找第一个大于$r$的数的位置,那么两个位置一减就是这个数在这个区间中的出现次数。比如查询区间$[2,4]$,我们先找到第一个大于等于2的数3,在vector中下标为2,再找第一个大于4的数为5,下标为3,那么3-2=1就是3这个数字在这个区间中的出现次数
那么,我们设$[L,R]$为查询区间之间的整块,因为我们第一步已经预处理出了所有块与块之间的答案,那么这一段之间的众数也就可以知道。那么,只有区间$[l,L-1]$和$[R+1,r]$之间的数字有可能更新答案。那么我们就去枚举这两个区间中的所有数字,然后用上面说的方法去查询它在整个查询区间内的出现次数,然后更新答案即可
复杂度为$O(n*n/T+n*T*logn)$,设块的大小为$n/sqrt{nlogn}$ ,那么时间复杂度就是$O(nsqrt{nlogn})$
其实还有一种更快的方法是先预处理出块与块之间的答案和各个数的出现次数,然后查询只要在散块里暴力增加并更新答案,之后暴力复原即可(然而我懒并不想打)
然后基本注意点都写在注解里了
1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<algorithm> 5 #include<cstring> 6 #include<vector> 7 #include<cmath> 8 #define inf 0x3f3f3f3f 9 using namespace std; 10 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 11 char buf[1<<21],*p1=buf,*p2=buf; 12 inline int read(){ 13 #define num ch-‘0‘ 14 char ch;bool flag=0;int res; 15 while(!isdigit(ch=getc())) 16 (ch==‘-‘)&&(flag=true); 17 for(res=num;isdigit(ch=getc());res=res*10+num); 18 (flag)&&(res=-res); 19 #undef num 20 return res; 21 } 22 char sr[1<<21],z[20];int C=-1,Z; 23 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 24 inline void print(int x){ 25 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 26 while(z[++Z]=x%10+48,x/=10); 27 while(sr[++C]=z[Z],--Z);sr[++C]=‘\n‘; 28 } 29 const int N=40005,M=1005; 30 int ans[M][M],a[N],b[N],cnt[N],rt[N],vis[N]; 31 vector<int> pos[N]; 32 int n,m,q,lastans=0,s,l,r; 33 inline int query_cnt(int x){ 34 //查询数的出现次数,注意l和r要开全局变量 35 return upper_bound(pos[x].begin(),pos[x].end(),r)-lower_bound(pos[x].begin(),pos[x].end(),l); 36 } 37 void init(){ 38 //暴力枚举块与块之间的答案 39 for(int i=1;i<=rt[n];++i){ 40 memset(cnt,0,sizeof(cnt)); 41 int bg=s*(i-1)+1,res=a[bg]; 42 for(int j=bg;j<=n;++j){ 43 ++cnt[a[j]]; 44 if(cnt[a[j]]>cnt[res]||(cnt[a[j]]==cnt[res]&&a[j]<res)) res=a[j]; 45 ans[i][rt[j]]=res; 46 } 47 } 48 } 49 int query(int l,int r){ 50 //查询,小块暴力,大块直接找答案 51 if(rt[r]-rt[l]<=1){ 52 int id=0,res=0; 53 for(int i=l;i<=r;++i) 54 if(!vis[a[i]]){ 55 int t=query_cnt(a[i]); 56 if(t>res||(t==res&&a[i]<id)) res=t,id=a[i]; 57 vis[a[i]]=1; 58 } 59 for(int i=l;i<=r;++i) vis[a[i]]=0; 60 return b[id]; 61 } 62 int L=rt[l]+1,R=rt[r]-1; 63 int LL=(L-1)*s+1,RR=R*s; 64 int id=ans[L][R],res=query_cnt(id);vis[id]=1; 65 for(int i=l;i<LL;++i) 66 if(!vis[a[i]]){ 67 int t=query_cnt(a[i]); 68 if(t>res||(t==res&&a[i]<id)) res=t,id=a[i]; 69 vis[a[i]]=1; 70 } 71 for(int i=RR+1;i<=r;++i) 72 if(!vis[a[i]]){ 73 int t=query_cnt(a[i]); 74 if(t>res||(t==res&&a[i]<id)) res=t,id=a[i]; 75 vis[a[i]]=1; 76 } 77 for(int i=l;i<LL;++i) vis[a[i]]=0; 78 for(int i=RR+1;i<=r;++i) vis[a[i]]=0; 79 vis[ans[L][R]]=0; 80 return b[id]; 81 } 82 int main(){ 83 n=read(),q=read(),s=sqrt(n/(double)(log2(n))+1); 84 //我怕s会变成0所以sqrt里加了个1(可能并不需要) 85 for(int i=1;i<=n;++i) a[i]=b[i]=read(),rt[i]=(i-1)/s+1;//分块 86 sort(b+1,b+1+n),m=unique(b+1,b+1+n)-b-1; 87 for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+m,a[i])-b,pos[a[i]].push_back(i); 88 //以上是离散 89 init(); 90 while(q--){ 91 l=read(),r=read(); 92 l=(l+lastans-1)%n+1,r=(r+lastans-1)%n+1; 93 if(l>r) swap(l,r); 94 print(lastans=query(l,r)); 95 } 96 Ot(); 97 return 0; 98 }
标签:min sizeof names 二分查找 code stdin target 离散 blank
原文地址:https://www.cnblogs.com/bztMinamoto/p/9607299.html