题目大意:
我们称一个子串是好的,当且仅当里面的连续子串不能求和为0。 现在给我们一个串an,问我们里面有多少个好的连续子串。
len(an)<=1e5.
解题思路:
首先,我们要用一种贡献法的思维。更具体地,我们发现,假如在an中部分区间[l,r]求和为0,以[l,r]为基础的向外拓张的串都是不好的串,例如[l-1,r] , [l,r+1]等等这种,所以我们的好的串必定不能包含[l,r]。 [l,r]贡献出了不好的串。
那么我们怎么快速找到[l,r]之间的数求和为0呢。很明显,我们可以使用前缀和结构,令前缀和为pre[n],那么pre[r] == pre[l-1] 就代表[l,r]中的数求和为0. 那么到这里,我们就知道我们需要在前缀和中找到含有相等的数的区间[l,r],l左端点对答案的贡献为:r - l - 1.
那么现在有一个n^2复杂度的算法,我们需要继续优化。我们发现,再找到一对相同的数字时,右指针是不需要重新归位的。毕竟我们前面已经走过了,证明那一段区间是不包含重复数的。这时候我们只需要让左指针往前走即可,直到我们把重复的数字去掉,然后再去走右指针。
双指针在走的时候,我们同时也要考虑是用右指针作贡献,还是左指针作贡献。
类似的双指针题目可以看
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
#include <bits/stdc++.h>
#define int long long
using namespace std;
int32_t main(){
int n;cin>>n;
map<int,int> mm;
vector<int> pre(n+2,0);
for(int i=1;i<=n;i++){
cin>>pre[i];
pre[i]+=pre[i-1];
}
int i = 0 ,j = 1;
map<int ,int> ms;
ms[0]=0;
int ans=0;
while(i<= n && j<=n){
if(!ms.count(pre[j])){
ans += (j - i ) ;
ms[pre[j]] = j;;
j++;
}else{
ms.erase(pre[i]);
i++;
}
}
cout<<ans<<endl;
return 0;
}