zoukankan      html  css  js  c++  java
  • Codeforces Round #632 (Div. 2) C. Eugene and an array(尺取法/前缀和)

    Eugene likes working with arrays. And today he needs your help in solving one challenging task.

    An array cc is a subarray of an array bb if cc can be obtained from bb by deletion of several (possibly, zero or all) elements from the beginning and several (possibly, zero or all) elements from the end.

    Let's call a nonempty array good if for every nonempty subarray of this array, sum of the elements of this subarray is nonzero. For example, array [1,2,3][−1,2,−3] is good, as all arrays [1][−1] , [1,2][−1,2] , [1,2,3][−1,2,−3] , [2][2] , [2,3][2,−3] , [3][−3] have nonzero sums of elements. However, array [1,2,1,3][−1,2,−1,−3] isn't good, as his subarray [1,2,1][−1,2,−1] has sum of elements equal to 00 .

    Help Eugene to calculate the number of nonempty good subarrays of a given array aa .

    Input

    The first line of the input contains a single integer nn (1n2×1051≤n≤2×105 )  — the length of array aa .

    The second line of the input contains nn integers a1,a2,,ana1,a2,…,an (109ai109−109≤ai≤109 )  — the elements of aa .

    Output

    Output a single integer  — the number of good subarrays of aa .

    Examples
    Input
    Copy
    3
    1 2 -3
    
    Output
    Copy
    5
    
    Input
    Copy
    3
    41 -41 41
    
    Output
    Copy
    3

    对于这个题先给出定义:

    子数列:为原数列在开头删去0~n个数,结尾删去0~n个数后得到的数列

    好数列:数列里没有和为0的子数列的数列称为好数列

    题意就是给一个数列,问里面包含多少好数列。

    跟着别的博客学了双指针法(尺取法),打算写的稍微详细一点。

    首先想到暴力解法,第一重循环枚举右端点,第二重循环枚举左端点,可以先求和为0的子序列的个数再容斥也可以直接求好数列的个数,前缀和预处理一下就能O(1)地查询连续区间和了。但是2e5必然T,考虑对其进行优化。实际上我们可以发现,如果是直接求好数列,第一重循环枚举右端点是省不了的,但第二重循环可以砍掉。我们没必要每次都从开始处枚举左端点,因为数列里只要有和为0的子数列就一定不能累加到答案里。举个例子,比如:

    a1 a2 a3 a4 a5 b1 b2 b3 a6 a7,其中b1~b3为和为0的序列,当第一重循环走到a7的位置时(a7为右端点),左端点只可能是b2 b3…a7,即起码是从某个和为0的子序列的左端点后一个数开始。事实上可能有很多和为0的子序列(可能交叉可能包含…),只需要从里面找到下标最大的一个左端点(不然的话一定会把和为0的序列包含进来),累加答案ans+=r-l即可。

    这时有两种情况,一是右端点包含在某个和为0的子序列里,二是不包含。假设我们用l表示下标最大的一个和为0的子序列的左端点,第二种情况比较简单,直接累加就行,关键是第一种情况。由前缀和的性质可知,假设ai~aj的和为0的话,sum[j]==sum[i-1]。这样我们利用map来记录前缀和和下标的对应关系。这样,循环里先用count查看当前前缀和是否在里面出现过,如果出现过就是第一种情况,说明当前右端点包含在一段和为0的子序列里,这时一定要先更新l的值:l=max(l,mp[sum[r]]+1);  mp[sum[r]]即右端点包含在的和为0的子序列的左端点前面一个数,+1就表示左端点了。这里要用max函数取最大,因为考虑到-1 -2 2 1这种和为0的子序列嵌套的问题,如果不取max,l就会由2被更新为1,显然不能保证l的最大性,所以要取max。记得循环最后要更新键值对以使l尽可能大。

    注意几个细节:一开始初始化l为0,r从1循环到n,这构造个例子就能理解;其次是要预先添加mp[0]=0因为一开始l初始化为0,当遇到右端点到a1这一段所有数的和为0的情况时正好能保证l更新为1; 最后一看2e5范围1e9的数据大小再求前缀和,肯定要开long long。

           

    #include <bits/stdc++.h>
    using namespace std;
    int n,a[200005];
    long long sum[200005]={0};
    map<long long,int>mp;
    int main()
    {
        cin>>n;
        int i;
        for(i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+(long long)a[i];
        long long ans=0;
        int l=0,r;//l是所有含0区间里最大的左端点 
        mp[0]=0;//初始化 假设序列是2 -2 ... sum[2]=0,则l为1,这之后就对mp[0]进行更新了 
        for(r=1;r<=n;r++)//当前区间以r为右端点 
        {
            if(mp.count(sum[r])==0)//如果当前前缀和在字典里没有出现过的话,说明目前没有以r为右端点的和为0的区间,所以是类似 1 2 -2 -1 3 4 ,r=4这种情况,就沿用上一次统计到的l=4 
            {
                ans+=r-l;
            }
            else
            {
                l=max(l,mp[sum[r]]+1); //一定要取max,考虑到-1 -2 2 1这种,如果不取max,l就会由2被更新为1,显然不可 
                ans+=r-l;//这时l已经被更新过了 
            }
            mp[sum[r]]=r;
        } 
        cout<<ans;
        return 0;
    }
  • 相关阅读:
    [BZOJ2729]排队
    [BZOJ2839]集合计数
    [BZOJ2111] Perm 排列计数
    Unet 项目部分代码学习
    数据增强代码
    论文阅读笔记五:U-Net: Convolutional Networks for Biomedical Image Segmentation(CVPR2015)
    CTPN项目部分代码学习
    论文阅读笔记四:CTPN: Detecting Text in Natural Image with Connectionist Text Proposal Network(ECCV2016)
    R2CNN项目部分代码学习
    VOC数据集生成代码使用说明
  • 原文地址:https://www.cnblogs.com/lipoicyclic/p/12669079.html
Copyright © 2011-2022 走看看