算法--前缀和技巧 (蓝桥杯123-灵能传输--求和)

什么是前缀和

如果一个数组a的元素为 a 1 , a 2 , a 3 , a 4 , a 5 . . . a i a_1,a_2,a_3,a_4,a_5...a_i a1,a2,a3,a4,a5...ai.
而另一个数组b为 a 1 , a 1 + a 2 , a 1 + a 2 + a 3 . . . . , a 1 + . . + a i a_1,a_1+a_2,a_1+a_2+a_3....,a_1+..+a_i a1,a1+a2,a1+a2+a3....,a1+..+ai
那么数组b就是a的前缀和了。

用途

那么前缀和有些什么用呢。
我们让 b 1 , b 2 . . . b i b_1,b_2...b_i b1,b2...bi代表b的元素
那么我们可以轻易的用一次减法得到某个区间的和如 a 5 + a 6 + . . . + a 100 a_5+a_6+...+a_{100} a5+a6+...+a100等于 b 100 − b 5 b_{100}-b_5 b100b5得到这个差值,而不用每次都一个个加。

什么时候用

  1. 经常需要求区间和
  2. 而在一些题目中,数组a在题意中是变化的,而其前缀和不变,那么很有可能就是使用前缀和

java的前缀和

在java的Arrays中有着这样一个方法。

Arrays.parallelPrefix

在这里插入图片描述
我们可以这样进行前缀和计算
怎么理解这个方法呢

就是将数组数从第二个开始依次执行
	前面一个的和后面数进行op操作,然后存放到第二个位置

如op为Integer::sum就是执行求和操作。
Arrays.parallelPrefix(a, Integer::sum);
//等价于
for(int i = 1;i<a.length;i++){
	a[i] = a[i-1]+a[i];
}

例题

[蓝桥杯 2022 省 A] 求和

题目描述

给定 n n n 个整数 a 1 , a 2 , ⋯   , a n a_{1}, a_{2}, \cdots, a_{n} a1,a2,,an, 求它们两两相乘再相加的和,即

S = a 1 ⋅ a 2 + a 1 ⋅ a 3 + ⋯ + a 1 ⋅ a n + a 2 ⋅ a 3 + ⋯ + a n − 2 ⋅ a n − 1 + a n − 2 ⋅ a n + a n − 1 ⋅ a n S=a_{1} \cdot a_{2}+a_{1} \cdot a_{3}+\cdots+a_{1} \cdot a_{n}+a_{2} \cdot a_{3}+\cdots+a_{n-2} \cdot a_{n-1}+a_{n-2} \cdot a_{n}+a_{n-1} \cdot a_{n} S=a1a2+a1a3++a1an+a2a3++an2an1+an2an+an1an

输入格式

输入的第一行包含一个整数 n n n

第二行包含 n n n 个整数 a 1 , a 2 , ⋯ a n a_{1}, a_{2}, \cdots a_{n} a1,a2,an

输出格式

输出一个整数 S S S,表示所求的和。请使用合适的数据类型进行运算。

样例 #1

样例输入 #1

4
1 3 6 9

样例输出 #1

117

提示

对于 30 % 30 \% 30% 的数据, 1 ≤ n ≤ 1000 , 1 ≤ a i ≤ 100 1 \leq n \leq 1000,1 \leq a_{i} \leq 100 1n1000,1ai100

对于所有评测用例, 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 1000 1 \leq n \leq 2\times10^5,1 \leq a_{i} \leq 1000 1n2×105,1ai1000

蓝桥杯 2022 省赛 A 组 C 题。

思路

可以知道22相乘呀。所以有就是求 a 1 ∗ ( a 2 + . . . + a n ) + a 2 ∗ ( a 3 + . . . + a n ) . . . a_1*(a_2+...+a_n) + a_2*(a_3+...+a_n)... a1(a2+...+an)+a2(a3+...+an)...
所以这个 a i + 1 + . . . . . + a n a_{i+1}+.....+a_n ai+1+.....+an用前缀和可以大大的减少计算。

代码

import java.io.*;
import java.util.Arrays;

public class Main {
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static void main(String[] args) throws IOException {
        //在此输入您的代码...
        int n = nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = nextInt();
        }
        int[] b = Arrays.copyOf(a, a.length);
        Arrays.parallelPrefix(b, Integer::sum);
        long ans = 0;
        for (int i = 0; i < n - 1; i++) {
            ans += (long) a[i] * (b[n - 1] - b[i]);
        }
        System.out.println(ans);
    }

}

[蓝桥杯 2021 国 ABC] 123

题目描述

小蓝发现了一个有趣的数列, 这个数列的前几项如下:

1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , … 1,1,2,1,2,3,1,2,3,4, \ldots 1,1,2,1,2,3,1,2,3,4,

小蓝发现, 这个数列前 1 1 1 项是整数 1 1 1 , 接下来 2 2 2 项是整数 1 1 1 2 2 2 , 接下来 3 3 3 项是整数 1 1 1 3 3 3 , 接下来 4 4 4 项是整数 1 1 1 4 4 4 , 依次类推。

小蓝想知道, 这个数列中, 连续一段的和是多少。

输入格式

输入的第一行包含一个整数 T T T, 表示询问的个数。

接下来 T T T 行, 每行包含一组询问, 其中第 i i i 行包含两个整数 l i l_{i} li r i r_{i} ri, 表示 询问数列中第 l i l_{i} li 个数到第 r i r_{i} ri 个数的和。

输出格式

输出 T T T 行, 每行包含一个整数表示对应询问的答案。

样例 #1

样例输入 #1

3
1 1
1 3
5 8

样例输出 #1

1
4
8

提示
对于 10 % 10 \% 10% 的评测用例, 1 ≤ T ≤ 30 , 1 ≤ l i ≤ r i ≤ 100 1 \leq T \leq 30,1 \leq l_{i} \leq r_{i} \leq 100 1T30,1liri100
对于 20 % 20 \% 20% 的评测用例, 1 ≤ T ≤ 100 , 1 ≤ l i ≤ r i ≤ 1000 1 \leq T \leq 100,1 \leq l_{i} \leq r_{i} \leq 1000 1T100,1liri1000
对于 40 % 40 \% 40% 的评测用例, 1 ≤ T ≤ 1000 , 1 ≤ l i ≤ r i ≤ 1 0 6 1 \leq T \leq 1000,1 \leq l_{i} \leq r_{i} \leq 10^{6} 1T1000,1liri106
对于 70 % 70 \% 70% 的评测用例, 1 ≤ T ≤ 10000 , 1 ≤ l i ≤ r i ≤ 1 0 9 1 \leq T \leq 10000,1 \leq l_{i} \leq r_{i} \leq 10^{9} 1T10000,1liri109
对于 80 % 80 \% 80% 的评测用例, 1 ≤ T ≤ 1000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 1000,1 \leq l_{i} \leq r_{i} \leq 10^{12} 1T1000,1liri1012
对于 90 % 90 \% 90% 的评测用例, 1 ≤ T ≤ 10000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 10000,1 \leq l_{i} \leq r_{i} \leq 10^{12} 1T10000,1liri1012
对于所有评测用例, 1 ≤ T ≤ 100000 , 1 ≤ l i ≤ r i ≤ 1 0 12 1 \leq T \leq 100000,1 \leq l_{i} \leq r_{i} \leq 10^{12} 1T100000,1liri1012
蓝桥杯 2021 国赛 A 组 E 题(B 组 F 题,C 组 F 题)。

思路

根据题目可以知道是得到某个范围的和。那么我们就可以考虑到前缀和。
我们看项,(num)项的和值是 1 , 1 + 2 , 1 + 2 + 3 , 1 + 2 + 3 + 4.... 1,1+2,1+2+3,1+2+3+4.... 1,1+2,1+2+3,1+2+3+4....
每一项的项数为 1 , 2 , 3 , 4... 1,2,3,4... 1234...个数。

但是我们需要的是从开头的项,那么用前缀和得到这样的表示数量的数组 1 , 3 , 6 , 10.... 1,3,6,10.... 1,3,6,10....。而我们发现这个数组其实和num数组一样的。我们需要求某个范围的和,那么我们构造一个前缀和(sum数组) 1 , 4 , 10 , 16... 1,4,10,16... 141016...如果想要求6到10项和就算sum[4]-sum[3]就可以了。如果想要5到8呢。
我们也可以先得到sum[4]-sum[3]然后减去9,10代表的加上5,6就是答案了。

那么我们只剩下一个问题了,如何得到4和3还有减去的数和加上的数。
我们发现这个围绕的都是6和10项。10是num[4]代表的,6是num[3]代表的。
加和减是和6,10的差距。

如何得到呢。我们可以发现num是顺序的,就可以用二分,得到其小于等于这个值的位置。(java直接Arrays.binarySearch插入点就可以了)

emm好像还差了,9、10、5、6代表的数如何求。我们可以知道,对于10的数一定是4,因为每一项都是从1到i来的,所以最后一个一定是i。第一个是1.
那么我们可以用一个求和公式来计算, ( 首项 + 末项 ) ∗ 项数 / 2 (首项+末项)*项数/2 (首项+末项)项数/2
项数num[i] - 当前项。末项为i,首项为i-项数。

下面来根据代码食用。

代码

import java.io.*;
import java.util.Arrays;

public class Main {
    static final PrintWriter print = new PrintWriter(System.out);
    // 更快的输入输出
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static long nextLong() throws IOException {
        st.nextToken();
        return (long) st.nval;
    }

    public static void main(String[] args) throws IOException {
        int m = 2000000;
        
        long[] num = new long[m];
        //构造前缀和num
        for (int i = 1; i < m; i++) {
            num[i] = num[i - 1] + i;
        }
        
        //构造前缀和sum
        long[] sum = Arrays.copyOf(num, m);
        Arrays.parallelPrefix(sum, Long::sum);
        
        //注释掉的是因为最大数据在10的12次方,测试m是否能够到达最大
//        System.out.println(num[m-1]);
//        System.out.println(sum[m-1]);
        int n = nextInt();
        for (int i = 0; i < n; i++) {
            long a = nextLong();
            long b = nextLong();

            //寻找下标。
            int x = Arrays.binarySearch(num, a);
            int y = Arrays.binarySearch(num, b);
            x = x > 0 ? x : -x - 1;
            y = y > 0 ? y : -y - 1;

			//获取区间和
            long ans = sum[y] - sum[x];

			//获取项数
            long z = num[y] - b;
            //求和
            ans -= (2 * y - z + 1) * z / 2;
            z = num[x] - a + 1;
            ans += (2 * x - z + 1) * z / 2;
            //获得答案
            print.println(ans);
        }
        print.flush();

    }
}

[蓝桥杯 2019 省 B] 灵能传输(蓝桥杯96%,洛谷ac)

我只做到96%,不能ac,最后一个超时了。但是在洛谷没有超时
在这里插入图片描述
在这里插入图片描述

题目描述

题目背景

在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 AOE 单位,在游戏的中后期发挥着重要的作用,其技能“灵能风暴”可以消耗大量的灵能对一片区域内的敌军造成毁灭性的伤害。经常用于对抗人类的生化部队和虫族的刺蛇飞龙等低血量单位

你控制着 n n n 名高阶圣堂武士,方便起见标为 1 , 2 , ⋯   , n 1,2, \cdots,n 1,2,,n。每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 a i a_i ai 表示其拥有的灵能的多少( a i a_i ai 非负表示这名高阶圣堂武士比在最佳状态下多余了 a i a_i ai 点灵能, a i a_i ai 为负则表示这名高阶圣堂武士还需要 − a i -a_i ai 点灵能才能到达最佳战斗状态)。现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i ∈ [ 2 , n − 1 ] i \in[2,n-1] i[2,n1],若 a i ≥ 0 a_i \ge 0 ai0 则其两旁的高阶圣堂武士,也就是 i − 1 i-1 i1 i + 1 i+1 i+1 这两名高阶圣堂武士会从 i i i 这名高阶圣堂武士这里各抽取 a i a_i ai 点灵能;若 a i < 0 a_i<0 ai<0 则其两旁的高阶圣堂武士,也就是 i − 1 , i + 1 i-1,i+1 i1,i+1 这两名高阶圣堂武士会给 i i i 这名高阶圣堂武士 − a i -a_i ai 点灵能。形式化来讲就是 ( a i − 1 , a i , a i + 1 ) ← ( a i − 1 + a i , − a i , a i + 1 + a i ) (a_{i-1},a_i,a_{i+1})\leftarrow (a_{i-1}+a_i,-a_i,a_{i+1}+a_i) (ai1,ai,ai+1)(ai1+ai,ai,ai+1+ai)

灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 max ⁡ i = 1 n { ∣ a i ∣ } \max\limits_{i=1}^n\{|a_i|\} i=1maxn{ai},请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。

输入格式

本题包含多组询问。输入的第一行包含一个正整数 T T T 表示询问组数。

接下来依次输入每一组询问。

每组询问的第一行包含一个正整数 n n n,表示高阶圣堂武士的数量。

接下来一行包含 n n n 个数 a 1 , a 2 , ⋯   , a n a_1,a_2, \cdots,a_n a1,a2,,an

输出格式
输出 T T T 行。每行一个整数依次表示每组询问的答案。
样例 #1
样例输入 #1

3 3
5 -2 3
4
0 0 0 0
3
1 2 3

样例输出 #1

3 0 3

样例 #2

样例输入 #2

3 4
-1 -2 -3 7
4
2 3 4 -8
5
-1 -1 6 -1 -1

样例输出 #2

5 7 4

样例 #3

样例输入 #3

见文件trans3.in。

样例输出 #3

见文件trans3.ans。

提示

【样例说明】

对于第一组询问:

2 2 2 号高阶圣堂武士进行传输操作后 a 1 = 3 a_1=3 a1=3 a 2 = 2 a_2=2 a2=2 a 3 = 1 a_3=1 a3=1。答案为 3 3 3

对于第二组询问:

这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。

【数据规模与约定】

对于所有评测用例, T ≤ 3 T \le 3 T3 3 ≤ n ≤ 3 × 1 0 5 3 \le n \le 3\times10^5 3n3×105 ∣ a i ∣ ≤ 1 0 9 |a_i| \le 10^9 ai109

蓝桥杯 2019 年省赛 B 组 J 题。

思路

我们看到这句话 ( a i − 1 , a i , a i + 1 ) ← ( a i − 1 + a i , − a i , a i + 1 + a i ) (a_{i-1},a_i,a_{i+1})\leftarrow (a_{i-1}+a_i,-a_i,a_{i+1}+a_i) (ai1,ai,ai+1)(ai1+ai,ai,ai+1+ai)
其前缀和 ( a i − 1 , a i − 1 + a i , a i − 1 + a i + a i + 1 ) ← ( a i − 1 + a i , a i − 1 + a i + a i + 1 ) (a_{i-1},a_{i-1}+a_i,a_{i-1}+a_{i}+a_{i+1})\leftarrow(a_{i-1}+a_i,a_{i-1}+a_{i}+a_{i+1}) (ai1,ai1+ai,ai1+ai+ai+1)(ai1+ai,ai1+ai+ai+1)
后面前缀和就用s表示了就是 s i − 1 , s i , s i + 1 ← s i , s i − 1 , s i + 1 s_{i-1},s_{i},s{i+1}\leftarrow s_{i},s{i-1},s{i+1} si1,si,si+1si,si1,si+1
我们可以发现,每次操作前缀和都只是换了个变罢了。

而我们题目的意思就是让2项的差值的最大值要最小。也就是 m a x ∣ s n − s n − 1 ∣ max|s_n - s_{n-1}| maxsnsn1最小
那么如何可以到最小呢。

猜测是顺序最小,我们来具体看看。看图,这个应该还是很明显的

在这里插入图片描述
我们对其顺序排序就可以了。但是也没有就此就结束了。最后一个是不能进行交换的,

还有个问题就是有负数。所以我们需要一个s0=0来衡量一下。
排序之后。
在这里插入图片描述
s0到min->sn->max->sn我们怎么来选择呢,这部分是有重复的。
我们是怎么来选呢,是隔一个选还是2个
我们来看1个的
在这里插入图片描述
2个的
在这里插入图片描述
2个的这个最大跨度肯定会比1个的要大呀,所以我们隔着一个选一个。

代码

import java.io.*;
import java.util.Arrays;

public class Main {
    static final PrintWriter print = new PrintWriter(System.out);
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static long nextLong() throws IOException {
        st.nextToken();
        return (long) st.nval;
    }


    public static void main(String[] args) throws IOException {
        int t = nextInt();
        for (int k = 0; k < t; k++) {
            int n = nextInt();
            long[] a = new long[n + 1];
            // 构造前缀和
            for (int i = 1; i <= n; i++) {
                a[i] = nextInt() + a[i - 1];
            }
//            for (int i = 1; i <= n; i++) {
//                a[i] = nextInt();
//            }
//            Arrays.parallelPrefix(a, Long::sum);
			
            long n0 = Math.min(a[0], a[n]);
            long nn = Math.max(a[0], a[n]);
            //排序
            Arrays.sort(a);
            //找到s0和sn
            int s0 = 0,sn = 0;
            for (int i = 0; i < n + 1; i++) {
                if (a[i] == n0) {
                    s0 = i;
                    break;
                }
            }
            for (int i = n; i >= 0; i--) {
                if (a[i] == nn) {
                    sn = i;
                    break;
                }
            }
            //找最大值,我们选用标记的方法来解决重复的问题
            long ans = 0;
            long[] b = new long[n + 1];
            boolean[] f = new boolean[n + 1];
            int l = 0, r = n;
            for (int i = s0; i >= 0; i -= 2) {
                b[l++] = a[i];
                f[i] = true;
            }
            for (int i = sn; i <= n; i += 2) {
                b[r--] = a[i];
                f[i] = true;
            }
            for (int i = 0; i <= n; i++) {
                if (!f[i])
                    b[l++] = a[i];
            }
            for (int i = 1; i <= n; i++) {
                ans = Math.max(ans,Math.abs(b[i]-b[i-1]));
            }
            print.println(ans);
        }
        print.flush();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值