前缀和&差分(学习笔记)

前缀和&差分(学习笔记)

1. 前缀和

前缀和其实就是高中数学里面学过的数列an的前n项和Sn

理解起来很好理解,但是想用的熟练就要多做题了

对于前缀和的储存,推荐使用STL模板 map

典例: CodeForces 776C Molly’s Chemicals

Molly Hooper has n different kinds of chemicals arranged in a line. Each of the chemicals has an affection value, The i-th of them has affection value a i.

Molly wants Sherlock to fall in love with her. She intends to do this by mixing a contiguous segment of chemicals together to make a love potion with total affection value as a non-negative integer power of k. Total affection value of a continuous segment of chemicals is the sum of affection values of each chemical in that segment.

Help her to do so in finding the total number of such segments.
Input

The first line of input contains two integers, n and k, the number of chemicals and the number, such that the total affection value is a non-negative power of this number k. (1 ≤ n ≤ 105, 1 ≤ |k| ≤ 10).

Next line contains n integers a 1, a 2, …, a n ( - 109 ≤ a i ≤ 109) — affection values of chemicals.
Output

Output a single integer — the number of valid segments.
Examples
Input

4 2
2 2 2 2

Output

8

Input

4 -3
3 -6 -3 12

Output

3

Note

Do keep in mind that k 0 = 1.

In the first sample, Molly can get following different affection values:

2: segments [1, 1], [2, 2], [3, 3], [4, 4];

4: segments [1, 2], [2, 3], [3, 4];

6: segments [1, 3], [2, 4];

8: segments [1, 4]. 

Out of these, 2, 4 and 8 are powers of k = 2. Therefore, the answer is 8.

In the second sample, Molly can choose segments [1, 2], [3, 3], [3, 4].

题意:对所有区间求和,寻找若干个区间和,使它们等于k的非负整数次幂,并输出这些区间的个数。

代码:

#include <iostream>
#include <cstdio>
#include <map>
#include <math.h>

using namespace std;
typedef long long LL;
const int maxx=1e5+5;
map<LL,int> mp;
//LL表示map的长度,int表示每个位置对应的值;这里int=1代表LL数据是否出现过
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    int a[maxx];
    //input
    for(int i=1 ; i<=n ; i++)
    {
        scanf("%d",&a[i]);
    }
    LL pow=1;
    LL ans=0;
    while(abs(pow)<1e15) //这里的上限值要尽可能高,否则会WA(1e14就不行)
    {
        mp.clear();
        mp[0]=1;
        LL sum=0;

        for(int i=1 ; i<=n ; i++)
        {
            sum+=a[i];
            //mp[sum-pow]代表:能否找到sum[l],使得sum[r]-k^n=sum[l]
            //sum[r]-sum[l]=k^n变形即上式
            ans+=mp[sum-pow];
            //当前的前缀和已经计算过,则做标记
            mp[sum]++;
        }
        pow*=k;
        if(pow==1)
            break; //若k=1,一次循环就可以完成任务
    }
    cout<<ans<<endl;
    return 0;
}

2. 差分

简介:
对于一个给定的数列an

差分序列cn的定义: c1 = a1 ; ci = ai - ai-1

前缀和与差分运算为互逆运算,任意一个数组a的前缀和数组的差分数组是它本身(即 Sci= Si - Si-1 = ai )

差分也是用来解决区间问题的,主要是用于区间的修改查询,并且会用到前缀和的知识

一些重要公式:(S代表原数组a的前缀和,Sc代表差分数组c的前缀和)

  1. Sn=Scn+Sn-1
  2. Scn=Scn-1+cn

基本流程:
1.预处理得到差分数组c[i]

2.区间修改
c[x] += z
c[y+1] -= z
x为区间的左边界,y为区间的右边界,z为要增加的数值

3.求差分数组的前缀和数组Sc[i],并由此得出原数组a的前缀和数组sum[i]

典例: HUD 1556 Color the ball

Problem Description
N个气球排成一排,从左到右依次编号为1,2,3…N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?

Input
每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。
当N = 0,输入结束。

Output
每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。

Sample Input

3
1 1
2 2
3 3
3
1 1
1 2
1 3
0

Sample Output

1 1 1
3 2 1

代码:

#include <iostream>
#include <string.h>
using namespace std;

int main()
{
    int n,a,b;
    int A[100005],C[100005],sumc[100005],sum[100005];
    int cnt[100005];
    scanf("%d",&n);
    while(n!=0)
    {
        memset(C,0,sizeof(C));
        memset(sumc,0,sizeof(sumc));
        memset(sum,0,sizeof(sum));
        memset(cnt,0,sizeof(cnt));
        //设置差分数组
        for(int i=1 ; i<=n ; i++)
        {
            A[i]=i;
            C[i]=A[i]-A[i-1];
        }
        //修改区间:区间内每个元素加1
        for(int i=1 ; i<=n ; i++)
        {
            scanf("%d%d",&a,&b);
            C[a]+=1;
            C[b+1]-=1;
        }
        //设置前缀和数组
        for(int i=1 ; i<=n ; i++)
        {
            sumc[i]=C[i]+sumc[i-1];  //差分数组的前缀和数组
            sum[i]=sumc[i]+sum[i-1]; //原数组的前缀和数组
        }
        for(int i=1 ; i<=n-1 ; i++)
            printf("%d ",sumc[i]-A[i]);
        printf("%d\n",sumc[n]-A[n]);
        scanf("%d",&n);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值