- 看我博客我有没看懂的地方,或者其他疑问,可以加我qq和我交流~我会及时解答
- qq:1244536605 (加好友时备注一下 博客 )
标签
- 难想的暴力。
简明题意
- D题没啥思路,放弃比赛直接开始写博客哈哈哈哈~
- 给定n长的数组a[]
- (接下来的文章中,我把连续子序列称为子串,大家注意一下哈)
- 题目定义了一个数组什么时候是good的。题目是这样定义的:一个数组b[],如果它是由a[]数组删掉头部几个元素,再从尾部删掉几个元素得到的,且b[]数组不包含一个和为0的子串,那么b[]是good的。
- 题目定义得很绕。。什么头部删几个,尾部删几个,,这都是误导的信息。实际上就是说,如果一个数组是good的,那么这个数组需要是a[]的一个子串,且这个数组本身不包含和为0的子串。题目中,头部删几个,尾部删几个,我开始还想了半天这是啥…后来才明白其实就是告诉你它是a[]的一个子串。
- 现在要求a[]有多少个子串是good的。
思路
- 包含a[i]的子串一共有多少个?
- 显然是n个。从a[i]往左边延伸得到i-1个,从a[i]往右延伸是n-i个,加上自己,就是n个。
- 那么我们可以枚举每一个a[i],求a[i]往左边可以延伸出多少个good的子串,再把对于每个a[i]求得的值加起来即可。
- 下面画一个图,蓝色部分表示我们正在考虑的a[i]。假设红色和棕色标出的区间和为0.相当于现在我们固定了子串的右端点,想要求出有多少个左端点可选,使得这个子串good。
- 那么很显然,左端点不可能在 L 3 L_3 L3的前面。那么是不是相当于我们在枚举a[i]的时候要维护,和为0的区间的最大左端点?是的。
- 假设我们维护的这个最大左端点是max_l,那么对于每一个a[i],我们让ans += i-max_l就好了。
- 嗯…我好像讲完了,就是这么简单,维护就好了。
---------------------------------------分割线-----------------------------------
- 可能难点在于怎么维护。我说说我的思路。我一看到区间和为0,我想到了前缀和,区间 [ i , j ] [i,j] [i,j]和为0等价于 s u m [ i − 1 ] = s u m [ j ] sum[i-1]=sum[j] sum[i−1]=sum[j]。那么我们可以先前缀和一下,然后考虑每一个a[i]的时候,直接找距离i最近的j且sum[j]=sum[i]。就能找到这个和为0的区间了,然后用这个更新max_l,再用max_l更新答案即可。
注意事项
- 无
总结
AC代码
#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<cstdio>
#include<set>
#include<map>
#include<string>
using namespace std;
const int maxn = 2e5 + 10;
long long dp[maxn];
map<long long, int> rec;//维护每个前缀和最近一次出现的位置
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> dp[i], dp[i] += dp[i - 1];
rec[dp[i]] = -1;
}
long long ans = 0;
int max_l = -1;
rec[0] = 0;
for (int i = 1; i <= n; i++)
{
if (rec[dp[i]] != -1)
max_l = max(max_l, rec[dp[i]]);
rec[dp[i]] = i;
ans += i - (max_l + 1);//max_l表示前缀和相等的下标,而max_l+1才是实际的区间
}
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}