SP20644 ZQUERY - Zero Query
SP20644 (回滚莫队)
题目很简单,给出一个只包含 \(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;
}