拓展一波回滚莫队:
对于一般插入和删除较为简单的操作我们可以使用普通莫队来搞。
但是如果题目要求讯问的求满足某些条件的最值,那么插入简单,删除的操作就很骚(即很难处理),大多数情况下都要使用某些神奇的数据结构或者???,这样复杂度就会提升至少一个log,然后1e5的数据就有点呛了,一般都跑不过去…
于是我们就人为的将区间的收缩变为扩张,对于每一个询问先按左端点所处块从小到大,相同按右端点从小到大。那么我们依次枚举每一个块,处理掉左端点在当前块的所有询问。
如果右端点和左端点都在同一个块内直接暴力统计答案, O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))。
否则对于每一个块,右指针起始指向当前块的末尾。然后之后右指针始终会向右扩展(排序的规则),按照普通莫队操作扩张即可。
每次到下一个块统计答案的时候要先清空前一个块的统计。
最妙的来了:由于左端点是无序的,我们首先将左指针放在下一个块的开头,保证了和右端点一开始无交集答案。对于一个询问,我们暴力将左指针移动到询问的左端点,统计左半区间的答案,然后和右半区间的答案进行一个合并,注意我们必须要保证左半区间的答案不会改变右半区间的答案。然后撤销左端点的移动,重新移动到原始位置(即下一个块开头),对于下一个询问也是同样的操作。
这里有一个小技巧:如何方便快捷的撤销左端点的移动?我们用一个新的数组统计左半区间的答案,然后与右半区间的答案进行合并得到最终的答案。对于每次询问我们都先清空新数组再去统计,这样左区间的答案就不会对右区间有影响,并且高效完成了撤销的任务。
一般时间复杂度:每次暴力统计相同块内的询问 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))。每一个块,右端点至多向右移动n步, O ( n ) O(n) O(n)。每一块的左端点暴力统计答案为 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))。至多有 s q r t ( n ) sqrt(n) sqrt(n)个块,故时间复杂度为 O ( n s q r t ( n ) + m s q r t ( n ) ) = O ( m s q r t ( n ) ) O(nsqrt(n) + msqrt(n))=O(msqrt(n)) O(nsqrt(n)+msqrt(n))=O(msqrt(n))级别的。
例题1: 求[l,r]区间中数值相同的数的最远距离。
/*求[l,r]里相同颜色的最远距离*/
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7,INF=1e9+7;
inline int read()
{
int sum=0; char ch;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') sum=(sum<<1)+(sum<<3)+ch-'0',ch=getchar();
return sum;
}
int n,m,Q,ans[N],blo[N],col[N],block,mx[N],mn[N],Ans=0,t[N];
struct fuk
{
int l,r,id;
}q[N];
inline bool cmp(fuk a,fuk b)
{
return blo[a.l]==blo[b.l]?a.r<b.r:a.l<b.l;
}
inline int query(int l,int r)//同一块里的暴力统计答案
{
int del=0;
for(int i=l;i<=r;i++) t[col[i]]=INF;
for(int i=l;i<=r;i++)
{
t[col[i]]=min(t[col[i]],i);
del=max(i-t[col[i]],del);
}
return del;
}
inline void add(int x)//加入了x这个点更新影响
{
mn[col[x]]=min(mn[col[x]],x);
mx[col[x]]=max(mx[col[x]],x);
Ans=max(Ans,mx[col[x]]-mn[col[x]]);
}
int main()
{
// freopen("far.in","r",stdin);
// freopen("far.out","w",stdout);
n=read(),m=read(),Q=read(); block=sqrt(n);
for(int i=1;i<=n;i++) col[i]=read();
for(int i=1;i<=Q;i++) q[i].id=i,q[i].l=read(),q[i].r=read();
for(int i=1;i<=n;i++) blo[i]=(i-1)/block+1;
sort(q+1,q+Q+1,cmp);
for(int i=1,id=1;i<=Q;id++)
{
int R=min(id*block,n),l=R+1,r=R;
Ans=0;
memset(mx,0,sizeof(mx));//清空上一个块的答案
memset(mn,0x3f,sizeof(mn));
while(i<=Q&&blo[q[i].l]==id)//在当前块中
{
if(blo[q[i].l]==blo[q[i].r])//同一个块中暴力统计
{
ans[q[i].id]=query(q[i].l,q[i].r);
i++; continue;
}
while(r<q[i].r) add(++r);//右移右端点,统计[R+1,q[i].r]区间的最值
int tmp=0;
for(int j=q[i].l;j<=l-1;j++) t[col[j]]=0x3f3f3f3f;//清空新数组
for(int j=q[i].l;j<=l-1;j++)//再统计[q[i].l--R],合并两个区间的答案
{
t[col[j]]=min(t[col[j]],j);
tmp=max(tmp,max(j-t[col[j]],mx[col[j]]-j));
}
//注意不能写Ans=max(Ans,tmp); ans[q[i].id]=Ans; 导致左区间影响了右区间
ans[q[i].id]=max(Ans,tmp);
i++;
}
}
for(int i=1;i<=Q;i++)
printf("%d\n",ans[i]);
}
例题2:分块入门9(统计 [ l , r ] [l,r] [l,r]区间的最小众数)
#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=1e5+7;
inline int read()
{
int sum=0; char ch;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') sum=(sum<<1)+(sum<<3)+ch-'0',ch=getchar();
return sum;
}
int mp[N];
int blo[N],block,n,a[N],t[N],mx[N],Ans=0,dat=0,ans[N],b[N],tot=0;
struct fuk
{
int l,r,id;
}q[N];
inline bool cmp(fuk a,fuk b)
{
return blo[a.l]^blo[b.l]?a.l<b.l:a.r<b.r;
}
inline int query(int l,int r)//暴力统计答案
{
int maxx=0,p=0;
for(int i=l;i<=r;i++) t[a[i]]=0;
for(int i=l;i<=r;i++)
{
t[a[i]]++;
if(t[a[i]]>maxx) maxx=t[a[i]],p=a[i];
else if(t[a[i]]==maxx) p=min(p,a[i]);
}
return mp[p];
}
inline void add(int x)
{
mx[a[x]]++;
if(mx[a[x]]>Ans) Ans=mx[a[x]],dat=a[x];
else if(mx[a[x]]==Ans) dat=min(a[x],dat);
}
inline void pre()
{
for(int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
for(int i=1;i<=n;i++) q[i].id=i,q[i].l=read(),q[i].r=read();
for(int i=1;i<=n;i++) blo[i]=(i-1)/block+1;
sort(q+1,q+n+1,cmp); sort(b+1,b+n+1);
//离散化
tot=unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+tot+1,a[i])-b; tot=0;
for(int i=1;i<=n;i++)
if(i==1||b[i]!=b[i-1])
mp[++tot]=b[i];
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
n=read(); block=sqrt(n);
pre();
int l=0,r=0;
for(int i=1,id=1;i<=n;id++)
{
int R=min(id*block,n),l=R+1,r=R;
Ans=0;dat=0; memset(mx,0,sizeof(mx));//清空上一个块的答案
while(i<=n&&blo[q[i].l]==id)
{
if(blo[q[i].l]==blo[q[i].r])//暴力统计答案
{
ans[q[i].id]=query(q[i].l,q[i].r);
i++; continue;
}
while(r<q[i].r) add(++r);//右移得到右区间答案
int tmp=0,tmpp=0;
for(int j=q[i].l;j<=l-1;j++) t[a[j]]=0;//清空新数组
for(int j=q[i].l;j<=l-1;j++)//统计左区间的答案
{
t[a[j]]++;
if(t[a[j]]+mx[a[j]]>tmp)
{
tmp=t[a[j]]+mx[a[j]];
tmpp=a[j];
}
else if(tmp==t[a[j]]+mx[a[j]])
tmpp=min(tmpp,a[j]);
}
//合并答案
if(tmp>Ans) ans[q[i].id]=mp[tmpp];
else if(tmp==Ans) ans[q[i].id]=mp[min(dat,tmpp)];
else ans[q[i].id]=mp[dat];
i++;
}
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}