码迷,mamicode.com
首页 > 其他好文 > 详细

【ZQUERY - Zero Query】题解

时间:2021-03-16 11:56:39      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:query   思路   有用   for   添加   前缀和   ons   cal   复杂   

Description

400 AC 祭

题面 : 【Link】

长度为 \(n\) 的序列,序列中的值为 1 或 -1。
\(m\) 个询问,询问在 \([L,R]\) 中区间和为 0 的区间的最大长度。

\(1 \leq n,m \leq 5 \times 10 ^ 4\),\(1 \leq l \leq r \leq n\)

Solution

一般来说看到这种在区间里的的一些操作,并多次询问,如果看到题目中的序列长度不超过 \(10 ^ 6\),且不强制在线,就可以用莫队来解决。

首先想到求前缀和,这样我们就可以 \(O(1)\) 查询这个区间内部的总和是多少,如果求 \([L,R]\) 内的总和,直接就算 \(sum_R - sum_{L - 1}\) 就可以了。

如果用莫队做,我们发现想要加入一个数很容易,我们可以直接加入,用一个桶来记录,但是我们发现想要删除一个点的话很难,因为我们每次删除的时候总会发现我们不知道要更新哪个值,只能暴力枚举,复杂度直接上天。

这样我们可以用回滚莫队。

回滚莫队是一种只添加但是并不删除的莫队,我们用分块的方式把整个序列分为 \(\sqrt{n}\) 个块。(也可以不是 \(\sqrt n\),根据题目来看)。

接下根据分块对询问进行排序,我们要保证以左区间所在的块为第一关键字,如果左区间所在块相同,那么就按照右端点递增排序。(这里千万不要排错,会 WA )。

bool CMP(Query a,Query b)
{
  return block[a.l_] ^ block[b.l_] ? block[a.l_] < block[b.l_] : a.r_ < b.r_;
}

最后就是我们的询问操作了 :

我们枚举每一个块,我们设这个块的右端点为 \(BLO\),那么这一次的指针为 \(l = BLO + 1\),右端点为 \(r = BLO\)

分为两种情况 :

  • 当询问的左端点和右端点在同一个块内部的时候,我们直接暴力查询就可以了,因为这时候查询的最大区间也就是 \(\sqrt n\),不会太大。
  • 当不在同一个区间内部的时候,就要移动指针了。
    • 因为我们排序的时候我们的右端点是单调递增的,我们先移动右指针,\(r\) 不断增加,不断统计答案,直到移动到目标右端点,这是我们要记录一下答案(后面解释)。
    • 同样也移动左指针到目标左端点,更新答案,这时候我们的查询操作就完成了,可以统计答案了。

统计答案后操作 :

因为我们移动了左指针,但是我们并没有按照左端点排序,我们的左端点是没有任何递增的特性的,这样的答案肯定会对后面的查询造成干扰,所以我们要不断增加 \(l\),使其回到初始位置 \(BLO\)

但是这时我们的答案还没有更新,我们前面记录的只移动 \(r\) 后的答案就有用了,我们的右端点单调递增,不会造成干扰,所以就把前面记录的值取回来就行了。

这就是整个回滚莫队的流程。

回到这个题目,很显然我们可以和回滚莫队模板题有同样的思路。

我们开两个数组 \(st[i]\)\(ed[i]\) 记录前缀和为 \(i\) 的元素在区间内部的第一次出现的位置和最后一次出现的位置,查询时不断取 \(\max\) 就行了。和模板题一模一样。

坑点

  • 前缀和查询的区间是 \([L,R]\),但是我们要求的是 \(L - 1\)\(R\)
  • 注意前缀和可能为负数所以一定要加上 50000,因为最多可能有 50000 个 -1,说多了都是泪啊。
  • 因为我们查询的时候是从 \(L - 1\)\(R\),所以查询的时候一定要注意。
  • 有可能会出现 -1,这时候要输出 0,我也不知道为啥会出现 -1。

Code

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxk = 5e5 + 100;
int n,m,len,T;
int sum[Maxk],a[Maxk];
int L[Maxk],R[Maxk],block[Maxk];
int ans[Maxk];
int st[Maxk],ma[Maxk];
struct Query{
  int l_;
  int r_;
  int pos_;
}s[Maxk];
inline int read()
{
	int s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == ‘-‘, ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
int Max(int x,int y) {return x > y ? x : y;}
void BLOCK()
{
  len = sqrt(n);
  T = n / len;
  for(int i = 1;i <= T;i ++) {
    L[i] = (i - 1) * len + 1;
    R[i] = i * len;
  }
  if(R[T] < n) T ++,L[T] = R[T - 1] + 1,R[T] = n;
  for(int i = 1;i <= T;i ++) 
    for(int j = L[i];j <= R[i];j ++)
      block[j] = i;
  return; 
}
bool CMP(Query a,Query b)
{
  return block[a.l_] ^ block[b.l_] ? block[a.l_] < block[b.l_] : a.r_ < b.r_;
}
int calc(int l,int r)
{
  int cnt[Maxk];
  int maxn = -1;
  for(int i = l - 1;i <= r;i ++) cnt[sum[i]] = 0;
  for(int i = l - 1;i <= r;i ++) {
    if(!cnt[sum[i]]) cnt[sum[i]] = i;
    else maxn = Max(maxn,i - cnt[sum[i]]);
  }
  return maxn;
}
void solve()
{
  sort(s + 1,s + m + 1,CMP);
  for(int i = 1,j = 1;j <= T;j ++) {
    int br = min(n,j * len),l = br + 1,r = l - 1,Ans = 0;
    memset(ma,0,sizeof ma);
    memset(st,0,sizeof st);
    for(;block[s[i].l_] == j;i ++) {
      if(block[s[i].r_] == j) {
        ans[s[i].pos_] = calc(s[i].l_,s[i].r_);
        continue;
      }
      while(r < s[i].r_) {
        r ++;
        ma[sum[r]] = r;
        if(!st[sum[r]]) st[sum[r]] = r;
        Ans = max(Ans,r - st[sum[r]]); 
      }
      int tmp = Ans;//先保存一下,因为右区间的贡献不会被刷新,但左区间的会
      while(l >= s[i].l_) {
        l --;
        if(ma[sum[l]]) Ans = max(Ans,ma[sum[l]] - l);
        else ma[sum[l]] = l;//最后出现的位置可能在左区间中
      }
      ans[s[i].pos_] = Ans;
      while(l <= br) {
      if(ma[sum[l]] == l) ma[sum[l]] = 0;
        l ++;
      }
      Ans = tmp;
    }
  }
  return;
}
signed main()
{
//  freopen("498.in","r",stdin);
//  freopen("409.out","w",stdout);
  n = read(),m = read();
  sum[0] = 50011;
  for(int i = 1;i <= n;i ++) sum[i] = read(),sum[i] += sum[i - 1];;
  for(int i = 1;i <= m;i ++) {
    s[i].l_ = read();
    s[i].r_ = read();
    s[i].pos_ = i;
  }
  BLOCK();
  solve();
  for(int i = 1;i <= m;i ++) {
    if(ans[i] < 0) cout << 0 << endl;
    else cout << ans[i] << endl;
  }
  return 0;
}

【ZQUERY - Zero Query】题解

标签:query   思路   有用   for   添加   前缀和   ons   cal   复杂   

原文地址:https://www.cnblogs.com/Ti-despair/p/14532277.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!