[BZOJ4241]历史研究(回滚莫队)

本文深入探讨了回滚莫队算法,一种用于处理区间查询的高效算法。通过将序列分为根号n大小的块,并对所有查询进行排序,算法在处理左端点在同一块内的查询时采用暴力方法,而在处理跨块查询时,通过预先设置左端点到块尾,再逐步减小的方式更新答案,避免了传统莫队算法中删除操作带来的额外log复杂度。文章还提供了完整的C++实现代码。

将整个序列分成$\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 }

 

 

转载于:https://www.cnblogs.com/HocRiser/p/9897941.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值