SP20644 ZQUERY - Zero Query

SP20644 (回滚莫队)

SP20644 (Luogu)

题目很简单,给出一个只包含 \(1\)\(-1\) 的序列,询问区间中区间和为 \(0\) 的区间最大长度。

求区间和这一操作可以在读入的时候预处理前缀和,要求 \([l,r]\) 的区间和为 \(0\) ,即要求 \(sum_{l-1}=sum_r\) ,这样就将问题转化为了求给定区间中最远相同数字之间的间隔。

考虑使用莫队算法,显然增加操作是很简单的,可以很容易地进行转移。但是删除操作不好完成,因此普通莫队的方法被舍弃掉。而对于这一类问题,回滚莫队可以很有效地解决。

回滚莫队最主要的用途是求一类增加容易删除困难(增加困难删除容易)的问题。既然另一种操作不好完成,那我就只用一种操作。

首先将询问离线,以左端点所在块为第一关键字,右端点为第二关键字进行排序。对于左端点在相同块中的询问,显然右端点只需要向右递增即可,最大时间复杂度为 \(\text O(n)\) ,而对于左端点,因为删除操作不好完成,所以处理完每一次询问之后,我们都将左端点拉回当前块的最右端,让它从最右端重新向左找,最大时间复杂度为 \(\text O(\sqrt n)\) ,因此这一步的时间复杂度为 \(\text O(n)\) ,又因为有 \(\sqrt n\) 个块,因此总时间复杂度为 \(\text O(n\sqrt n)\)

对于一些细节,首先因为原序列中含有 \(-1\) ,因此前缀和可能为负数,用桶统计的时候需要加上 \(5e4\) 来避免数组越界。另外一点就是在读入时需要将 \(n\) 以及区间右边界 \(+1\) ,这样可以将 \(sum_{l-1}\) 给挪到 \(sum_l\) 的位置,方便处理。

#pragma GCC optimize(2)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<limits.h>
#include<cmath>
#include<time.h>
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
template<typename T> void read(T &k)
{
 	k=0;
	T flag=1;char b=getchar();
	while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
	while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
	k*=flag;
}
const int _SIZE=5e4;
int n,m,len;
int a[_SIZE+5],sum[_SIZE+5],pos[_SIZE+5],R[_SIZE+5];
int ma[(_SIZE<<2)+5],mi[(_SIZE<<2)+5];
int ans[_SIZE+5];
struct QUERY{
	int l,r,id;
}que[_SIZE+5];
bool cmp(QUERY x,QUERY y)
{
	if (pos[x.l]!=pos[y.l]) return x.l<y.l;
	return x.r<y.r;
}
int main()
{
	//freopen("SP20644.in","r",stdin);
	//freopen("SP20644.out","w",stdout);
	//double st=clock();
	read(n),read(m);++n;
	len=sqrt(n);
	for (int i=2;i<=n;i++)
	{
		read(a[i]);sum[i]=sum[i-1]+a[i];
		pos[i]=(i-1)/len+1;
	}
	for (int i=1;i<=n;i++) sum[i]+=_SIZE;
	//for (int i=1;i<=n;i++) printf("%d ",sum[i]); exit(0);
	for (int i=1;i<=m;i++)
	{
		read(que[i].l);
		read(que[i].r);
		que[i].r++;
		que[i].id=i;
	}
	for (int i=1;i<=len+1;i++) R[i]=min(n,i*len);
	sort(que+1,que+m+1,cmp);
	int l=0,r=0,lastblock=0,temp=0;
	for (int i=1;i<=m;i++)
	{
		//printf("\n%d~%d ",que[i].l,que[i].r);
		if (pos[que[i].l]==pos[que[i].r])
		{
			//printf("same block\n");
			temp=0;
			for (int j=que[i].l;j<=que[i].r;j++) ma[sum[j]]=0;
			for (int j=que[i].r;j>=que[i].l;j--)
				if (!ma[sum[j]]) ma[sum[j]]=j;
				else temp=max(temp,ma[sum[j]]-j);
			for (int j=que[i].l;j<=que[i].r;j++) ma[sum[j]]=0;
			ans[que[i].id]=temp;
			continue;
		}
		if (lastblock^pos[que[i].l])
		{
			//system("pause");
			while (r>R[pos[que[i].l]])
			{
			//	printf("%d ",r);
				ma[sum[r]]=mi[sum[r]]=0;
				r--;
			}
		
			while (l<R[pos[que[i].l]]+1)
			{
				ma[sum[l]]=mi[sum[l]]=0;
				l++;
			}
			lastblock=pos[que[i].l];
			r=l-1;
			temp=0;
		}
		while (r<que[i].r)
		{
			++r;
			if (!mi[sum[r]]) mi[sum[r]]=ma[sum[r]]=r;
			else ma[sum[r]]=r,temp=max(temp,r-mi[sum[r]]);
		}
		int res=temp;
		while (l>que[i].l)
		{
			--l;
			if (!ma[sum[l]]) ma[sum[l]]=l;
			else res=max(res,ma[sum[l]]-l);
		}
		while (l<R[pos[que[i].l]]+1)
		{
			if (ma[sum[l]]==l) ma[sum[l]]=0;
			++l;
		}
		ans[que[i].id]=res;
	}
	for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
	//printf("%.0lfms\n",clock()-st);
	return 0;
}
posted @ 2022-07-06 15:40  Hanx16Msgr  阅读(36)  评论(0编辑  收藏  举报