CF1333C Eugene and an array[前缀和][尺缩法]

简述题意:

给定一个长度为 n 的数组 ar (n<=2e5)

问这个数组 ar 中有多少子数组是好数组

子数组的定义为:

  把一个数组前面删去0个或全部元素,后面删去0个或全部元素得到的数组就是原数组的子数组

好数组的定义为:

  对于数组 a 的每个子数组 b 都满足 sum{b} ≠ 0

  则数组 a 就是个好数组

每个数组元素保证 abs(a[i])<=1e9

题意来源于

思路:由于题目要求的是连续的子序列的和不为0,所以我们很容易想到用前缀和维护,但是关键问题在于怎么有序不重复地统计good数组。

显然最朴素的算法是对于每个以J为右端点的子数组,都从左往右找符合good数组的左端点I,并统计J-I的最大值,这种算法有两个问题,一是如何找到最小的符合good数组的左端点,二是最坏复杂度为N^2。 

在仔细思考后我们要发现一个性质:如果一段区间为[L,R]的子数组满足good要求,那么其前缀和序列[L,R]一定不能存在两个相同的数。 因为如果前缀和区间一旦存在某个Sum[i] == Sum[j]的话,则说明a[i+1]+a[i+2]....+a[j] = 0,那么与条件矛盾。

那么问题就转化为:对于每个以J为右端点的子数组,如何找到最小的左端点I,满足前缀和区间[I,J]的数字都不相同。

如果我们对于每个右端点J,都从第1到第J-1找左端点的话时间又会超时。所以我们需要思考如何快速地找左端点,假设上次的右端点为J-1,左端点为L,那么这次的右端点为J,而左端点一定不会向左走(要不是L,要不是L+x)。所以我们可以用尺缩法维护题目!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

map<ll, bool>vis;
ll Sum[200005], x, Out;
int n;


int main(){
    scanf("%d", &n);
    for(int i = 1; i <=n ;i++){
        scanf("%lld", &x);
        Sum[i] = Sum[i - 1] + x;
    }
    vis[0] = true;
    int L = 0, R = 1;
    while(R <= n){
        while( vis[Sum[R]] == true) vis[Sum[L++]] = false;
        vis[Sum[R]] = true;
        Out += (R - L);
        R++;
    }
    printf("%lld", Out);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LYFer233/p/12666515.html
今日推荐