回滚莫队(小学生一看就懂了!)

拓展一波回滚莫队:

对于一般插入和删除较为简单的操作我们可以使用普通莫队来搞。

但是如果题目要求讯问的求满足某些条件的最值,那么插入简单,删除的操作就很骚(即很难处理),大多数情况下都要使用某些神奇的数据结构或者???,这样复杂度就会提升至少一个log,然后1e5的数据就有点呛了,一般都跑不过去…

于是我们就人为的将区间的收缩变为扩张,对于每一个询问先按左端点所处块从小到大,相同按右端点从小到大。那么我们依次枚举每一个块,处理掉左端点在当前块的所有询问。

如果右端点和左端点都在同一个块内直接暴力统计答案, O ( s q r t ( n ) ) O(sqrt(n)) Osqrt(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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值