• 蓝桥杯备赛系列——倒计时50天!


    蓝桥杯备赛系列

    倒计时50天!

    前缀和和差分

    知识点

    前缀和数组:
    假设原数组用a[i]表示,前缀和数组用sum[i]表示,那么sum[i]表示的是原数组前i项之和,注意一般用前缀和数组时,原数组a[i]的有效下标是从1开始的。式子如下,
    s u m [ n ] = ∑ i = 1 n a [ i ] sum[n]=\sum_{i=1}^n a[i] sum[n]=i=1na[i]
    差分数组:
    假设原数组用a[i]表示,差分数组用d[i]表示,那么d[i]表示的是a[i]和a[i-1]之差,注意一般用差分数组时,原数组a[i]的有效下标也是从1开始的。式子如下,
    d [ n ] = a [ i ] − a [ i − 1 ] d[n]=a[i]-a[i-1] d[n]=a[i]a[i1]
    接下来看一下前缀和和差分的模板题。
    ps:
    二维前缀和参考视频
    二维差分参考视频

    前缀和模板

    题目描述

    给定一个长度为 n n n的数组 a 1 , a 2 , . . . . a n . a_1,a_2,....a_n. a1,a2,....an.

    接下来有 q q q次查询, 每次查询有两个参数 l , r l, r l,r.

    对于每个询问, 请输出 a l + a l + 1 + . . . + a r . a_l+a_{l+1}+...+a_r. al+al+1+...+ar.

    输入描述

    第一行包含两个整数 n n n q q q.

    第二行包含n个整数, 表示 a 1 , a 2 , . . . . a n . a_1,a_2,....a_n. a1,a2,....an.

    接下来q行,每行包含两个整数 l和r.

    1 ≤ n , q ≤ 1 0 5 1≤n,q≤10^5 1n,q105
    − 1 0 9 ≤ a [ i ] ≤ 1 0 9 −10^9≤a[i]≤10^9 109a[i]109
    1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn

    输出描述

    输出 q q q行,每行代表一次查询的结果.

    样例输入

    3 2
    1 2 4
    1 2
    2 3
    
    • 1
    • 2
    • 3
    • 4

    样例输出

    3
    6
    
    • 1
    • 2
    题目分析

    前缀和最经典的一个作用就是以O(1)的时间复杂度求区间和。

    sum[l-1]=a[1]+a[2]+a[3]+…+a[l-1]

    sum[r]=a[1]+a[2]+a[3]+…+a[l-1]+a[l]+…+a[r]

    sum[r]-sum[l-1]=a[1]+a[2]+a[3]+…+a[l-1]+a[l]+…+a[r]-(a[1]+a[2]+a[3]+…+a[l-1])

    ​ =a[l]+a[l+1]+…+a[r]

    所以区间[l,r]的和可以用sum[r]-sum[l-1]表示。

    题目代码
    import java.util.Scanner;
    public class Main{
    public static void main(String[] args) {
    	Scanner scanner = new Scanner(System.in);
    	int n = scanner.nextInt();
    	int q = scanner.nextInt();
    	int a[] = new int[n+1];
    	long sum[] = new long[n+1];
    	for(int i = 1;i <= n;i++) a[i] = scanner.nextInt();
    	for(int i = 1;i <= n;i++) sum[i] = sum[i-1] + a[i];
    	while(q-- > 0) {
    		int l = scanner.nextInt();
    		int r = scanner.nextInt();
    		System.out.println(sum[r]-sum[l-1]);
    	}
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    二维前缀和模板

    题目描述

    给你一个 n 行 m 列的矩阵 A ,下标从1开始。

    接下来有 q 次查询,每次查询输入 4 个参数 x 1 , y 1 , x 2 , y 2 x1 , y1 , x2 , y2 x1,y1,x2,y2

    请输出以 ( x 1 , y 1 ) (x1, y1) (x1,y1)为左上角 ,$ (x2,y2)$ 为右下角的子矩阵的和,

    输入描述

    第一行包含三个整数n,m,q.

    接下来n行,每行m个整数,代表矩阵的元素

    接下来q行,每行4个整数 x 1 , y 1 , x 2 , y 2 x1, y1, x2, y2 x1,y1,x2,y2,分别代表这次查询的参数

    1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000
    1 ≤ q ≤ 1 0 5 1≤q≤10^5 1q105
    − 1 0 9 ≤ a [ i ] [ j ] ≤ 1 0 9 −10^9≤a[i][j]≤10^9 109a[i][j]109
    1 ≤ x 1 ≤ x 2 ≤ n 1≤x1≤x2≤n 1x1x2n
    1 ≤ y 1 ≤ y 2 ≤ m 1≤y1≤y2≤m 1y1y2m

    输出描述

    输出q行,每行表示查询结果。

    样例输入

    3 4 3
    1 2 3 4
    3 2 1 0
    1 5 7 8
    1 1 2 2
    1 1 3 3
    1 2 3 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    样例输出

    8
    25
    32
    
    • 1
    • 2
    • 3
    题目分析

    我们通过图片分析如何求二维前缀和数组以及如何利用二维前缀和数组求矩阵和。

    如何求二维前缀和数组。如图所示,假设要求 s u m [ x 1 ] [ y 1 ] sum[x1][y1] sum[x1][y1]的值,也就是图中大红色圈起来的部分,我们可以将蓝色部分加上绿色部分后再加上x1,y1处本来的值,也就是 s u m [ x 1 ] [ y 1 − 1 ] + s u m [ x 1 − 1 ] [ y 1 ] + a [ x 1 ] [ y 1 ] sum[x1][y1-1]+sum[x1-1][y1]+a[x1][y1] sum[x1][y11]+sum[x11][y1]+a[x1][y1]。但是这样会多加上一部分,也就是蓝色和绿色重叠的部分,他用 s u m [ x 1 − 1 ] [ y 1 − 1 ] sum[x1-1][y1-1] sum[x11][y11]表示,需要减掉。综上 s u m [ x 1 ] [ y 1 ] = s u m [ x 1 ] [ y 1 − 1 ] + s u m [ x 1 − 1 ] [ y 1 ] + a [ x 1 ] [ y 1 ] − s u m [ x 1 − 1 ] [ y 1 − 1 ] sum[x1][y1]=sum[x1][y1-1]+sum[x1-1][y1]+a[x1][y1]-sum[x1-1][y1-1] sum[x1][y1]=sum[x1][y11]+sum[x11][y1]+a[x1][y1]sum[x11][y11]

    如何利用二维前缀和数组求矩阵和。如图所示,假设要求左上角下标为x1,y1,右下角下标为x2,y2的矩阵的值,也就是图中蓝色的部分。可以用 s u m [ x 2 ] [ y 2 ] − s u m [ x 2 ] [ y 1 − 1 ] − s u m [ x 1 − 1 ] [ y 2 ] + s u m [ x 1 − 1 ] [ y 1 − 1 ] sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1] sum[x2][y2]sum[x2][y11]sum[x11][y2]+sum[x11][y11]表示,也就是图中绿色部分减去图中橙色和紫色部分,而紫色和橙色部分有重叠,多减了,要再加回来,也就是图中棕色部分。

    题目代码

    代码细节,数据的读入涉及到矩阵,对于java而言数据量较大,要使用快读。如果没有学过快读,可以点击链接学习一下。

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class Main {
    public static void main(String[] args) throws IOException {
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	String[] strings = br.readLine().split(" ");
    	int n = Integer.parseInt(strings[0]);
    	int m = Integer.parseInt(strings[1]);
    	int q = Integer.parseInt(strings[2]);
    	long a[][] = new long[n+1][m+1];
    	long sum[][] = new long[n+1][m+1];
    	for(int i = 1;i <= n;i++) {
    		strings = br.readLine().split(" ");
    		for(int j = 1;j <= m;j++) {
    			a[i][j] = Integer.parseInt(strings[j-1]);
    		}
    	}
    	
    	for(int i = 1;i <= n;i++) {
    		for(int j = 1;j <= m;j++) {
    			sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
    		}
    	}
    	while(q-- > 0) {
    		strings = br.readLine().split(" ");
    		int x1 = Integer.parseInt(strings[0]);
    		int y1 = Integer.parseInt(strings[1]);
    		int x2 = Integer.parseInt(strings[2]);
    		int y2 = Integer.parseInt(strings[3]);
    		System.out.println(sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]);
    	}
    }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    差分模板题

    题目描述

    给你一个长度为n的正数数组 a 1 , a 2 , . . . . a n a_1,a_2,....a_n a1,a2,....an

    接下来对这个数组进行m次操作,每个操作包含三个参数l,r,k,代表将数组中 a l + a l + 1 + . . . + a r a_l+a_{l+1}+...+a_r al+al+1+...+ar部分都加上k。

    请输出操作后的数组。

    输入描述

    第一行包含两个整数n和m。
    第二行包含n个整数表示 a 1 , a 2 , . . . . a n a_1,a_2,....a_n a1,a2,....an
    接下来是m行,每行三个整数,分别代表每次操作的参数l,r,k.

    1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105
    − 1 0 9 ≤ a [ i ] ≤ 1 0 9 −10^9≤a[i]≤10^9 109a[i]109
    1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1lrn
    − 1 0 9 ≤ k ≤ 1 0 9 −10^9≤k≤10^9 109k109

    输出描述

    输出1行,表示m次操作后的 a 1 , a 2 , . . . . a n a_1,a_2,....a_n a1,a2,....an

    输入样例

    3 2
    1 2 3
    1 2 4
    3 3 -2
    
    • 1
    • 2
    • 3
    • 4

    输出样例

    5 6 1
    
    • 1
    题目分析

    差分数组一般要结合前缀和使用,使用方法如下,

    假设我有两个操作,第一个操作是对下标为3一直到下标为10的数字都加上2,第二个操作是对下标为4到下标为7的数字都加上3,朴素的做法是遍历一遍要操作的数组区间,然后执行操作就可以了,两次操作过后,原始数组累计要改变的值如下,

    a的下标1234567891011
    a要加上的值00255552220

    这样做执行每次操作的时间复杂度是数组的长度。那么如何利用差分数组来做呢?

    对下标为3一直到下标为10的数字都加上2——>d[3]+=2,d[11]-=2。

    对下标为4到下标为7的数字都加上3——>d[4]+=3,d[8]-=3。

    然后求d数组的前缀和数组,结果如下,下标从1开始

    sum的下标1234567891011
    sum的值00255552220

    此时sum里面的值表示的是原始数组要变动的值,将sum数组与原始数组相加,即是操作后的结果,可以发现是和表1一样的。而这样每次操作的时间复杂度是O(1)。

    综上,如果我要对区间[l,r]里的数加上k,我只需要对差分数组进行操作,即d[l]+=k,d[r+1]-=k。

    题目代码
    import java.util.Scanner;
    
    public class Main {
    public static void main(String[] args) {
    	Scanner scanner = new Scanner(System.in);
    	int n = scanner.nextInt();
    	int m = scanner.nextInt();
    	long[] a = new long[n+1];
    	long[] s = new long[n+1];
    	for (int i = 1; i < a.length; i++) {
    		a[i] = scanner.nextInt();
    	}
    	while(m>0) {
    		m--;
    		int l = scanner.nextInt();
    		int r = scanner.nextInt();
    		int k = scanner.nextInt();
    	    s[l] += k;
    	    if(r+1<=n) {
    	    	s[r+1]-=k;
    	    }
    	}
    	long sum[] = new long[n+1];
    	for (int i = 1; i < s.length; i++) {
    		sum[i] = sum[i-1]+s[i];
    		a[i] = a[i]+sum[i];
    		System.out.print(a[i] +" ");
    	}
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    二维差分模板题

    题目描述

    给你一个n行m列的矩阵,下标从1开始。
    接下来有q次操作,每次操作输入5个参数x1, y1, x2, y2, k
    表示把以(x1, y1)为左上角,(x2,y2)为右下角的子矩阵的每个元素都加上k,
    请输出操作后的矩阵。

    输入描述

    第一行包含三个整数n,m,q.
    接下来n行,每行m个整数,代表矩阵的元素

    接下来q行,每行5个整数x1, y1, x2, y2, k,分别代表这次操作的参数

    1 ≤ n , m ≤ 1000 1≤n,m≤1000 1n,m1000
    1 ≤ q ≤ 1 0 5 1≤q≤10^5 1q105
    1 ≤ x 1 ≤ x 2 ≤ n 1≤x1≤x2≤n 1x1x2n
    1 ≤ y 1 ≤ y 2 ≤ m 1≤y1≤y2≤m 1y1y2m
    − 1 0 9 ≤ 矩阵中的元素 ≤ 1 0 9 −10^9≤矩阵中的元素≤10^9 109矩阵中的元素109

    输出描述

    输出n行,每行m个数,每个数用空格分开,表示这个矩阵。

    样例输入

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

    样例输出

    9 8 6
    8 7 5
    
    • 1
    • 2
    题目分析

    二维差分模板关键在于确定好在差分数组的哪些地方更改值。假设要更改的地方是从下标(2,4)到下标(3,6)的位置加1,那么首先必然是让(2,4)的位置加1,这样之后对差分数组求前缀和的结果如图所示,

    我们要更改的是绿色圈起来的部分,但是之外的部分也更改了,我需要把这部分更改变回来,我想让(2,6)之后的从(2,7)开始都是0,既然(2,4)加了1,那么对应的(2,7)这一部分之后我要减掉1,来抵消前面的加1。同样,(4,4)也要减1来抵消(2,4)的加1。这样就可以了吗?这样相当于有两个位置减1,一个位置加1,一看也不对呀,对于(4,7)这个位置应该是没有变化的,但是它的前缀和所包含的格子里(2,4)加了1,(2,7)减了1,(4,4)也减了1,相当于(4,7)这个位置减了1,我们要把这个减1抵消,所以要在(4,7)这里加1。

    综上,对(x1,y2)到(x2,y2)的格子都进行加k操作,只需要(x1,y1)+=k,(x1,y2+1)-=k,(x2+1,y1)-=k,(x2+1,y2+1)+=k。

    然后对差分数组求二维前缀和之后,把结果累加到原始数组上就可以了。

    题目代码
    import java.util.Scanner;
    public class Main{
    	public static void main(String[] args) {
    		Scanner scanner = new Scanner(System.in);
    		int n = scanner.nextInt();
    		int m = scanner.nextInt();
    		int q = scanner.nextInt();
    		long[][] a = new long[n + 1][m + 1];
    		long[][] b = new long[n + 1][m + 1];
    		long[][] sum = new long[n + 1][m + 1];
    		for (int i = 1; i <= n; i++) {
    			for (int j = 1; j <= m; j++) {
    				a[i][j] = scanner.nextLong();
    			}
    		}
    		while ((q--) != 0) {
    			int x1 = scanner.nextInt();
    			int y1 = scanner.nextInt();
    			int x2 = scanner.nextInt();
    			int y2 = scanner.nextInt();
    			int k = scanner.nextInt();
    			b[x1][y1] += k;
    			if (y2 + 1 <= m) {
    				b[x1][y2 + 1] -= k;
    			}
    			if (x2 + 1 <= n) {
    				b[x2 + 1][y1] -= k;
    			}
    			if (x2 + 1 <= n && y2 + 1 <= m) {
    				b[x2 + 1][y2 + 1] += k;
    			}
    		}
    
    		for (int i = 1; i <= n; i++) {
    			for (int j = 1; j <= m; j++) {
    				sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+b[i][j];
    				a[i][j] += sum[i][j];
    				System.out.print(a[i][j] + " ");
    			}
    			System.out.println();
    		}
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    abb

    题目描述

    leafee 最近爱上了 abb 型语句,比如“叠词词”、“恶心心”

    leafee 拿到了一个只含有小写字母的字符串,她想知道有多少个 “abb” 型的子序列?
    定义: abb 型字符串满足以下条件:

    1. 字符串长度为 3 。
    2. 字符串后两位相同。
    3. 字符串前两位不同。

    输入描述

    第一行一个正整数 n

    第二行一个长度为 n 的字符串(只包含小写字母)

    1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105

    输出描述

    “abb” 型的子序列个数。

    样例输入

    6
    abcbcc
    
    • 1
    • 2

    样例输出

    8
    
    • 1

    共有1个abb,3个acc,4个bcc

    题目分析

    在abcbcc中,acc的个数为3,他是哪3个呢?可以看下图,a的后面有3个c,在这3个c里面任选两个c可以和a组成一个叠词,那么组合的种类为 C 3 2 C_3^2 C32也就是 3 ∗ 2 / 2 = 3 3*2/2=3 32/2=3种。假设字母a后面有num个相同的字母,那么它能够贡献的叠词个数为 C n u m 2 C_{num}^2 Cnum2,也就是 n u m ∗ ( n u m − 1 ) / 2 num*(num-1)/2 num(num1)/2个。因此我们需要求出每个字母后面出现的其它相同字母的个数。

    在这里插入图片描述

    对于上图字符串,因为求的是后面的字符出现的次数,所以我们倒序遍历去求。位置6c出现了一次,用 n u m [ 6 ] [ c ] = 1 num[6][c]=1 num[6][c]=1表示,位置5c出现了1次,用 n u m [ 5 ] [ c ] = 1 num[5][c]=1 num[5][c]=1表示,位置3c出现了一次,用 n u m [ 3 ] [ c ] = 1 num[3][c]=1 num[3][c]=1表示,其余位置 n u m [ 1 , 2 , 4 ] [ c ] = 1 num[1,2,4][c]=1 num[1,2,4][c]=1都是0。那么求位置4后面包括位置4在内c出现的次数,应该是位置6的c累加上位置5的c,即 n u m [ 5 ] [ c ] + n u m [ 6 ] [ c ] num[5][c]+num[6][c] num[5][c]+num[6][c],求位置1后面c出现的次数,应该是 n u m [ 2 ] [ c ] + n u m [ 3 ] [ c ] + n u m [ 4 ] [ c ] + n u m [ 5 ] [ c ] + n u m [ 6 ] [ c ] = 0 + 1 + 0 + 1 + 1 = 3 num[2][c]+num[3][c]+num[4][c]+num[5][c]+num[6][c]=0+1+0+1+1=3 num[2][c]+num[3][c]+num[4][c]+num[5][c]+num[6][c]=0+1+0+1+1=3。这里其实就是前缀和数组或者说是后缀和数组,因为求的是后面的值累加的结果。

    综上,我们用 n u m s [ i ] [ c ] nums[i][c] nums[i][c]表示下标为i的位置的右边字符c出现的总次数。这里的数组就是后缀和数组。即 n u m s [ i ] [ c ] = n u m [ i + 1 ] [ c ] + n u m [ i + 2 ] [ c ] + . . . + n u m [ n ] [ c ] nums[i][c]=num[i+1][c]+num[i+2][c]+...+num[n][c] nums[i][c]=num[i+1][c]+num[i+2][c]+...+num[n][c]

    那么 n u m s [ i ] [ c ] = n u m s [ i + 1 ] [ c ] + n u m [ i ] [ c ] nums[i][c]=nums[i+1][c]+num[i][c] nums[i][c]=nums[i+1][c]+num[i][c]

    题目代码
    import java.util.Scanner;
    public class abb {
    public static void main(String[] args) {
    	Scanner scanner = new Scanner(System.in);
    	int n = scanner.nextInt();
    	char[] s = (" "+scanner.next()).toCharArray();
    	int pre[][] = new int[n+2][26];
    	for(int i = n;i >= 1;i--) {
    		for(int j = 0;j < 26;j++) {
                //求下标为i的位置包括下标i在内26个字母中,每个字母出现的次数。这里要用到前缀和
    			pre[i][j] =pre[i+1][j];
    		}
    		pre[i][s[i]-'a']++;//这里就是加上位置i的字母
    	}
    	long sum = 0;
    	for(int i = 1;i <= n;i++) {
    		for(int j = 0;j < 26;j++) {
                //注意这里要有j!=s[i]-'a'的判断,因为aaa是不合法的
                //同样也要有pre[i][j]>=2的判断,因为后面至少要有两个才能组合出acc
                //pre[i+1][j]是i+1,因为pre[i][j]包含了位置i的字母,但是我们求的是i后面的字母
                //但其实用pre[i][j]也没有问题,因为这里的字母j肯定和位置i的字母不一样,那么必然不会对pre[i][j]数组产生作用,因为这里的num[i][j]=0
    			if(pre[i][j]>=2&&j!=s[i]-'a') sum += (pre[i+1][j]-1)*pre[i
    +1][j]/2;
    		}
    	}
    	System.out.println(sum);
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    鼠鼠我鸭

    题目描述

    在一个叫做酱西功爷枝叶鸡树学院的地方有n只小动物,要么是鼠鼠,要么是鸭鸭,从1到n编号,每只小动物有个体重 a i a_i ai

    在这个学校里,存在一种神奇的魔法,可以将编号位于某个区间 [ l , r ] [l,r] [l,r]内的所有鼠鼠都变为鸭鸭,鸭鸭都变为鼠鼠(魔法并不会改变体重)。

    现在你可以施放这个魔法至多1次。(也可以不施放)

    问最终鸭鸭的总重量最多是多少?

    输入格式

    第一行一个整数T表示样例个数。 ( 1 ≤ T ≤ 10 ) (1≤T≤10) (1T10)

    对于每个样例:

    第一行一个整数n表示小动物的个数。( 1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105)

    第二行 n n n个整数,表示第 i i i个小动物的类型。0表示鼠鼠,1表示鸭鸭。

    第三行 n n n个整数,表示第 i i i个小动物的体重 a i a_i ai。( 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109)

    输出格式

    对于每个样例一行一个整数表示答案。

    样例输入

    2
    3
    0 0 0
    1 2 3
    4
    0 1 0 0
    2 5 6 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    样例输出

    6
    16
    
    • 1
    • 2
    题目分析

    假设原本所有鸭鸭的重量是sum,操作后形成的偏移是fix,即经过至多1次操作后鸭鸭的总重量是sum+fix,这里的fix最小是0,因为如果操作后的fix是负数,会使得鸭鸭的总重量减少,倒不如不操作,直接是sum就可以了。

    来看这个fix应该怎么求。以样例的第二组数据为例,如果操作区间是[0,4],那么每个位置对应的偏移量分别为2,-5,6,5。总的偏移量为2-5+6+5=8。如果操作区间是[1,2],总的偏移量为-5+6=1。其实要求某个区间的偏移量就是数组[2,-5,6,5]对应区间的区间和。这里的[2,-5,6,5]也叫做偏移量数组,因为对于位置i的值而言,如果他是鸭鸭,一次操作后就变为了鼠鼠,那么之前累加上的他的重量应该减去,所以偏移量就是负的该位置的重量也就是-a[i],如果他是鼠鼠,一次操作后就变为了鸭鸭,他的重量应该累加上,所以偏移量就是该位置的重量也就是a[i]。所以我们可以求一下偏移量数组的前缀和,然后利用前缀和求最大的偏移量区间和,也就是一开始说的fix,累加到原始的重量和sum里面就好。

    假设偏移量的前缀和数组是pre[i],那么偏移量的区间和[l,r]就用pre[r]-pre[l-1],如果我们遍历l和r的话,双层嵌套for循环会超时,所以这里应该采用类似双指针的做法,假设固定了r,要想pre[r]-pre[l-1]最大,需要pre[l-1]是区间[1,l-1]里面最小的那个数,用mi表示,关键代码如下,

    fix=0;mi=0;
    for(int i=1;i<=n;i++)  
    		{
    			fix=max(fix,s[i]-mi);//i固定时,mi是下标比i小的数中的最小值
    			mi=min(mi,s[i]);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    fix最小是0,所以初始化为0。这里的mi最小值也为0,mi为0时表示的是区间[1,i]的区间和,如果mi不为负数,说明s[i]-mi会变小,即比s[i]本身小,倒不如直接用s[i],s[i]表示的就是前i项之和,也是区间和。

    题目代码
    #include 
    using namespace std;
    typedef long long ll;
    
    const int N=1e5+10;
    ll g[N],w[N],s[N],p[N];
    ll sum,fix,mi;
    
    int main(){
        int t;cin>>t;
        while(t--){
            int n;cin>>n;
            for(int i=1;i<=n;i++)cin>>g[i];
            for(int i=1;i<=n;i++)cin>>w[i];
            for(int i=1;i<=n;i++)
    		{
    			s[i]=s[i-1]+(g[i] == 1 ? -1 : 1)*w[i]; //计算出施加魔法后的偏移值(鸭子重量),如果原来是1(鸭子)就要减去,原来是0(鼠)就加上
    		}
    		sum=0;
            for(int i=1;i<=n;i++) {
                sum+=w[i]*g[i];
            }
            fix=0,mi=0;
            for(int i=1;i<=n;i++)
    		{
    			fix=max(fix,s[i]-mi);
    			mi=min(mi,s[i]);
    		}
    
            cout<<sum+fix<<'\n';
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    代码有一个易错点,因为变量都是全局变量,所以sum,fix,e在每一组测试样例中都要重置为初始值。

    import java.util.Scanner;
    public class Main{
    
    
    	static  int N=(int) (1e5+10);
    	static long[] g=new long[N],w=new long[N],s=new long[N],p=new long[N];
    	static long sum,fix,mi;
    public static void main(String[] args) {
    	 int t;
    	 Scanner scanner = new Scanner(System.in);
    	 t = scanner.nextInt();
    	    while(t-- > 0){
    	        int n;
    	        n = scanner.nextInt();
    	        for(int i=1;i<=n;i++)g[i]=scanner.nextLong();
    	        for(int i=1;i<=n;i++)w[i]=scanner.nextLong();
    	        for(int i=1;i<=n;i++)
    			{
    				s[i]=s[i-1]+(g[i] == 1 ? -1 : 1)*w[i]; //计算出施加魔法后的偏移值(鸭子重量),如果原来是1(鸭子)就要减去,原来是0(鼠)就加上
    			}
    			sum=0;
    	        for(int i=1;i<=n;i++) {
    	            sum+=w[i]*g[i];
    	        }
    	        fix=0;mi=0;
    	        for(int i=1;i<=n;i++)  //从第一个遍历到最后,计算出最大最小值
    			{
    				fix=Math.max(fix,s[i]-mi);
    				mi=Math.min(mi,s[i]);
    			}
                System.out.println(sum+fix);
    	    }
    
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    如果题目分析有错误或者没有表达清楚的地方,欢迎在评论区里指出!

  • 相关阅读:
    端口被占用怎么解决
    PHP GET,POST请求file_get_contents拼接header
    国外问卷调查赚钱靠谱吗?
    【顺序表的实现】
    深入了解 Postman 中的变量
    Mybatis插入数据后返回主键
    AppAnalytics设备的分析服务
    Vue:组件缓存
    DSP开发环境及工具之CCS
    (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • 原文地址:https://blog.csdn.net/qq_53237241/article/details/136227916