https://www.luogu.com.cn/problem/P4168
题目
给$n$个数字,有$m$次询问,问$a_l, a_{l+1} , dots , a_r$的众数是什么,
$1leqslant n leqslant 40000, 1leqslant m leqslant 50000, 1leqslant a_ileqslant10^9$
题解
第一次做分块
方法一
因为n不是很大,所以可以对数据进行离散化后统计出现次数
所以就可以直接统计最大的了。这样复杂度是$mathcal{O}(m imes n)$,肯定超时
可以尝试提前分块打出一些表,比如分成$t$块,然后提前打好$inom{t}{2}$块的最大值,并保存是哪一个
那么每次查询的时候最多花$2 imes lfloor n/t floor$的时间,时间复杂度是$mathcal{O}(t^2n+2mn/t)$
把$m$和$n$看作同数量级,设为N,那么得到$t^2N+2N^2/t$,为了保证数量级相同,设$t^2N=2N^2/t$,得到$t=sqrt[3]{N}$
因为大于或小于以后两边渐进复杂度都会增加,导致整个表达式的渐进复杂度增加(算法导论:证明$max(a,b)=Theta(a+b)$)
AC代码
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> using namespace std; #define REP(i,a,b) for(register int i=(a); i<(b); i++) #define REPE(i,a,b) for(register int i=(a); i<=(b); i++) #define PERE(i,a,b) for(register int i=(a); i>=(b); i--) #ifdef sahdsg #define DBG(...) printf(__VA_ARGS__) #else #define DBG(...) void(0) #endif typedef long long ll; #define MAXN 40007 #define MAXM 50007 #define MAXD 35 int a[MAXN], b[MAXN], c[MAXN]; int d[MAXD][MAXD][MAXN]; int now[MAXN]; int t,l,na; int x=0; inline void did(int f) { now[f]++; if(now[na]<now[f] || (now[na]==now[f] && now[na+1]>f)) { now[na+1]=f; now[na]=now[f]; } } inline int go(int z, int y) { int i=(z+l-1)/l, j=y/l; int L=i*l, R=j*l; if(i<j) { REP(k,0,na+2) now[k] = d[i][j][k]; REP(f,z,L) did(c[f]); REP(f,R,y) did(c[f]); } else { memset(now,0,sizeof now); REP(f,z,y) did(c[f]); } return x=b[now[na+1]]; } int main() { int n,m; scanf("%d%d", &n, &m); REP(i,0,n) {scanf("%d", &a[i]); b[i]=a[i];} sort(b,b+n); na = unique(b,b+n)-b; REP(i,0,n) { c[i] = lower_bound(b,b+na,a[i])-b; } memset(d,0,sizeof d); t = pow((double)n, (double)1/3); l = t ? n/t : n; REP(i,0,t) REPE(j,i,t) { REP(f,i*l,j*l) { int k = c[f];// DBG("*%d ", k); d[i][j][k]++; if(d[i][j][k]>d[i][j][na] || (d[i][j][k]==d[i][j][na] && k<d[i][j][na+1])) { d[i][j][na] = d[i][j][k]; d[i][j][na+1] = k; } } } REP(i,0,m) { int l0, r0; scanf("%d%d", &l0, &r0); int l = (l0 + x - 1) % n + 1; int r = (r0 + x - 1) % n + 1; if(l>r) swap(l,r); go(l-1,r); printf("%d ", x); } return 0; }
方法二
用同样的分块方法,但是只记录最大值,不记录次数,而使用二分确定大小,一次二分确定大小需要$mathcal{O}(log n)$。
设需要分$D$块,然后得到时间复杂度$mathcal{O}(D imes N+2MN/Dlog n)$(因为剩下部分最长是两个块,虽然比平均情况大,但是为了应付数据,数据是最大的很多……)
那么用相同的方法,解得$D=sqrt{2Nlog N}$
中间有个细节,就是计算D和L的时候要考虑是偏大还是篇小,由于我快断电了,所以坑了
AC代码
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<cassert> using namespace std; #define REP(i,a,b) for(register int i=(a); i<(b); i++) #define REPE(i,a,b) for(register int i=(a); i<=(b); i++) #define PERE(i,a,b) for(register int i=(a); i>=(b); i--) #ifdef sahdsg #define DBG(...) printf(__VA_ARGS__) #else #define DBG(...) void(0) #endif typedef long long ll; #define MAXN 50007 #define MAXM 50007 #define MAXL 1007 #define MAXD 884*2 int a[MAXN],b[MAXN],c[MAXN],nb; int fk[MAXD][MAXD], L, D; int cnt[MAXN], nmax, ncnt; int x=0; vector<int> arr[MAXN]; int calc(int l, int r, int b) { //r--; return lower_bound(arr[b].begin(), arr[b].end(), r)-lower_bound(arr[b].begin(), arr[b].end(), l); } void did(int l, int r, int b) { int t=calc(l,r,b); if(t>ncnt || (t==ncnt && b<nmax)) { ncnt=t; nmax=b; } } void work(int l, int r) { nmax=0, ncnt=0; int z=(l+L-1)/L, y=r/L; if(z<y) { int Z=z*L, Y=y*L; REP(i,l,Z) did(l,r,c[i]); REP(i,Y,r) did(l,r,c[i]); did(l,r,fk[z][y]); } else { REP(i,l,r) did(l,r,c[i]); } x=b[nmax]; } int main() { int n,m; scanf("%d%d", &n, &m); REP(i,0,n) { scanf("%d", &a[i]); b[i]=a[i]; } sort(b,b+n); nb = unique(b,b+n)-b; REP(i,0,n) { c[i] = lower_bound(b,b+nb,a[i])-b; } D = sqrt(log((double)n)/log(2.0)*n*2); if(D==0) D=1; L = n/D; //L<L' D>D' D = n/L; //L>L' D<D' REP(i,0,D) { int s=i*L; nmax = 0, ncnt = 0; REP(k,0,n) cnt[k]=0; REP(j,s,n) { int J=(j+1+L-1)/L, k=c[j]; cnt[k]++; if(cnt[k]>ncnt ||(cnt[k]==ncnt && k<nmax)) { ncnt = cnt[k]; nmax = k; } fk[i][J]=nmax; } } REP(i,0,n) { arr[c[i]].push_back(i); } REP(i,0,m) { int l,r; scanf("%d%d", &l, &r); l = (l+x-1)%n+1, r=(r+x-1)%n+1; if(l>r) swap(l,r); work(l-1,r); printf("%d ", x); } }