回滚莫队
听 jmr 讲数据结构听到自闭,于是痛定思痛决定补一补技能点。
主要参考 ouuan 的博客。
算法流程
同普通莫队,对询问端点进行分块,对于左右端点在同一块的询问直接暴力计算。
bool cmp(Query a,Query b){return a.bl!=b.bl?a.l<b.l:a.r<b.r;}
排序之后,询问左端点 \(l\) 换块时清空答案,这时对于这一块的询问右端点 \(r\) 是单调递增的。对于同一块内的询问,先将莫队的 \(L\) 设为下一块的起始点,然后将莫队的 \(R\) 向右移动至 \(r\),然后 \(L\) 移动至 \(l\)。此次询问处理完之后将 \(ans\) 回滚会 \(L\) 移动之前的答案,并且将 \(L\) 重新设为下一块的起点。可以证明复杂度仍然是 \(\mathcal O(n^{1.5})\) 级别。
例题
LOJ#2874. 「JOISC 2014 Day1」历史研究
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#include<cstdlib>
using namespace std;
#define mp make_pair
#define pb push_back
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=1e5+10;
int cnt[N],t[N],a[N];
ll ans,Ans[N];
void add(int x)
{
cnt[x]++;
ans=max(ans,1ll*t[x]*cnt[x]);
}
struct Query{int l,r,pos,bl;}q[N];
bool cmp(Query x,Query y){return x.bl==y.bl?x.r<y.r:x.l<y.l;}
int main()
{
int n=read(),m=read(),sz=sqrt(n),cnt1=0;
for(int i=1;i<=n;i++)a[i]=t[i]=read();
int c=n;sort(t+1,t+c+1),c=unique(t+1,t+c+1)-t-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(t+1,t+c+1,a[i])-t;
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
if(l/sz==r/sz)
{
ans=0;
for(int j=l;j<=r;j++)cnt[a[j]]++,ans=max(ans,1ll*cnt[a[j]]*t[a[j]]);
Ans[i]=ans;
ans=0;
for(int j=l;j<=r;j++)cnt[a[j]]=0;
}
else q[++cnt1]=Query{l,r,i,l/sz};
}
sort(q+1,q+cnt1+1,cmp);
for(int i=1,L=1,R=0;i<=cnt1;i++)
{
// printf("[%d, %d]\n",q[i].l,q[i].r);
if(q[i].bl!=q[i-1].bl||i==1)
{
memset(cnt,0,sizeof(cnt));
L=(q[i].bl+1)*sz;
R=(q[i].bl+1)*sz-1;
ans=0;
}
while(R<q[i].r)add(a[++R]);
ll tmp=ans;
while(L>q[i].l)add(a[--L]);
Ans[q[i].pos]=ans;
while(L<(q[i].bl+1)*sz)cnt[a[L]]--,L++;
ans=tmp;
}
for(int i=1;i<=m;i++)printf("%lld\n",Ans[i]);
return 0;
}