简述题意:
给定一个长度为 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; }