[BZOJ4241]历史研究(回滚莫队)
将整个序列分成$\sqrt{n}$块,所有询问按左端点所在块为第一关键字,右端点所在块为第二关键字排序。
当右端点增加或左端点减小时,更新桶与答案。但是左端点增加时需要删除,这时就必须用堆等数据结构维护,复杂度多一个log。
回滚莫队是一个trick:
1.按同样的方法对所有询问排序,每次处理左端点在同一个块中的所有询问。
2.若对于当前询问,右端点和左端点在同一个块中,暴力即可。
3.否则,右端点照常加入更新,左端点先设为块尾,再不断减小,更新答案后再还原至块尾。
这样每次只有左端点减小时才会更新答案,不再需要删除操作。
复杂度分析基本与普通莫队相同。
对于左右端点所属块相同的询问,单次复杂度显然$O(\sqrt{n})$
对于左右端点所属块不同的询问,右端点的处理并没有区别,故总复杂度仍是$(n\sqrt{n})$。左端点每次都只在一个块中移动,故总复杂度也是$(n\sqrt{n})$
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=100010,B=300; 9 ll ans[N]; 10 int n,Q,cnt[N],a[N],b[N],bel[N]; 11 struct P{ int l,r,id; }q[N]; 12 bool cmp(const P &a,const P &b){ return (bel[a.l]==bel[b.l]) ? a.r<b.r : bel[a.l]<bel[b.l]; } 13 14 int main(){ 15 freopen("bzoj4241.in","r",stdin); 16 freopen("bzoj4241.out","w",stdout); 17 scanf("%d%d",&n,&Q); 18 rep(i,1,n) scanf("%d",&a[i]),b[i]=a[i],bel[i]=(i-1)/B+1; 19 rep(i,1,Q) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i; 20 sort(b+1,b+n+1); int tot=unique(b+1,b+n+1)-b-1; 21 rep(i,1,n) a[i]=lower_bound(b+1,b+tot+1,a[i])-b; 22 sort(q+1,q+Q+1,cmp); int i,j; 23 for (i=1; i<=Q; i=j){ 24 int t=bel[q[i].l],r=min(n,B*t),l=r+1; ll mx=0; 25 memset(cnt,0,sizeof(cnt)); 26 for (j=i; j<=Q && bel[q[j].l]==t; j++){ 27 if (bel[q[j].r]==t){ 28 rep(k,q[j].l,q[j].r) cnt[a[k]]++,mx=max(mx,1ll*b[a[k]]*cnt[a[k]]); 29 rep(k,q[j].l,q[j].r) cnt[a[k]]--; 30 ans[q[j].id]=mx; mx=0; 31 }else{ 32 while (r<q[j].r) cnt[a[++r]]++,mx=max(mx,1ll*b[a[r]]*cnt[a[r]]); 33 ll tmp=mx; 34 while (l>q[j].l) cnt[a[--l]]++,mx=max(mx,1ll*b[a[l]]*cnt[a[l]]); 35 ans[q[j].id]=mx; 36 while (l<=B*t) cnt[a[l++]]--; 37 mx=tmp; 38 } 39 } 40 } 41 rep(i,1,Q) printf("%lld\n",ans[i]); 42 return 0; 43 }