尺取法
B. Eugene and an array-cf 1333C
题意:一串数字 可正可负 如果一串数字的和不为零且他的所有连续子段的和也不为零 那么就称之为good 求给定的一串数的good连续子串有多少个
思路
可以先预处理前缀和 用pre记录下来 如果有两个前缀和是一样的 那么除去他们重叠部分后一段的区间和就是0即不是good的
我们要求的是good的 那我们可以找出恰好不good的即区间和正好是0但子区间和都不是0的区间 然后将在这区间中以该区间右边界为右边界的所有good串贡献给答案 不难发现这样的数量是 r-l+1 个(若当前恰好good的区间是[l, r],就是除了等于自己的子区间为0 其他子区间都不可能为0)而我们只要记录这一答案即可 因为如果l再往左移显然包含了一个区间和为0的区间就不是good的
当然还有普遍的情况就是一个区间已确认是good的 那么就直接贡献区间长度(它的子段肯定是good的)
左右边界都是任意的 便于计算和操作我们只要遍历右边界(即右边界暂时固定)就能求全所有符合情况且不重复
对于找正好不是good的区间:
如果两个前缀和相等且相邻那么它们未重叠的部分就是正好不是good的区间
我们可以开两个指针 l r以及一个mp记录某个前缀和的值是否出现过
遍历r 将r经过的前缀和都标记为1 即mp[pre[i]] = 1 当下次再遇到相同值的前缀和那么就知道前面已有了那么两个前缀和的未重叠部分就是要找的恰好不good串 (因为r是从左到右遍历里的那就可以保证这两个前缀和是相邻的)但是当前l r区间显然是比我们所求区间大的那我们就要缩小范围 将l右移 同时解除标记mp[pre[l]]因为当前l肯定是一个非good中的元素 不会再相应后面的相同的pre了否则得到非good串的就不是恰好非good的 直到解除某个前缀l后当前的mp[pre[i]]不再是1 说明当前的[l, r]就是要找的恰好非good串
而在r遍历的过程中如果当前pre[i]未被标记说明当前区间是good的那么就直接加贡献
#include<bits/stdc++.h>
#include<unordered_map>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-4;
const ll N = 1e6 + 5;
const int M = 1e5 + 5;
const int mod = 1e9 + 7;
ll n, a[N], pre[N];
map<ll, ll>mp;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
ll l = 0, r = 1;
ll ans = 0, flag = 0;
for (int r = 1; r <= n; r++) {
if (pre[r] == 0 && !flag && l == 0) {//当第一次出现前缀和为0且前面没有非good串时那这段是恰好good的就要贡献区间长-1 即r-1
l++;
flag = 1;
}
else
while (mp[pre[r]]) {//已知道有一个非good串 那就去找
mp[pre[l]] = 0;
l++;
ans += r - l;
continue;
}
mp[pre[r]] = 1;
ans += r - l;因为每次找到了左区间又加1了所以直接r-l
}
cout << ans << "\n";
}
signed main()
{
IOS;
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
}