一维前缀和/差分,二维前缀和/差分

纯纯笔记,不详细不详细

代码不是题解不全是题解(是题解的能ac)

一维:

前缀和:

预处理,算出从1到 i 所有数的总和

使用场景:

在查询某一区间,如:查询 arr数组中 从 下标left 到 下标 right 的 的所有数值之和

1.如果不使用前缀和,暴力循环,时间复杂度为 o(n)(遍历数组,从left遍历到right),这样有很多次重复计算。

2.如果使用前缀和,查询时时间复杂度为 o(1),即一次查询就算出了要求区间内的数值总和,

查询时 sum[right]-sum[left-1] 

#include <iostream>
using namespace std;
int sum[1000005];
int arr[1000005];//可以不创建arr数组,因为arr[i]每个位置的数字都只用了一次->用于计算sum[i]
int main()
{   
    int n = 0, a = 0;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin>>arr[i];
        sum[i] = sum [i-1] + arr[i];
    }
    cin >> left >> right;
    cout << sum[right] - sum[left-1];
	return 0;
}

一维差分练习:

洛谷P8218

差分:

前缀和的逆运算

使用场景:

(假设仍有 arr[100005] 数组)

差分用于同时对 (arr[1000005] )数组某一区间的数值进行操作,而不必暴力循环

例如:对arr[x]到arr[y]同时加上某一数值z

1.构建差分数组

构建arr[]的差分数组brr[]

brr[i]=arr[i]-arr[i-1](i从1开始,arr[0]=0=brr[0])

brr[1]~brr[i]的 前缀和 等于 arr[i],例如:

        arr[1]=brr[1](+brr[0],brr[0]=0)

        arr[2]=brr[2]+brr[1] ~~~~~ = brr[2]+arr[1]

        arr[3]=brr[3]+brr[2]+brr[1] ~~~~~ brr[3]+arr[2]

        ......

        arr[i]=brr[i]+arr[i-1]

2.原理

假设要对arr数组的 [l,r] 区间内所有值同时加上z,

1>首先,对brr[l]+z

这时如果 再使用brr[1]~brr[i]求前缀和,得新的arr[]数组

这等效于对arr[l]~arr[i]所有数值都加z

但现在不求brr的前缀和

2>然后,对brr[r+1]-z

这等效于对arr[r+1]~arr[i]所有数值减z

这时如果 再使用brr[1]~brr[i]求前缀和,得新的arr[]数组

这等效于对arr[l]~arr[i]所有数值都加z

现在对求brr的前缀和,得到的结果的就是对arr[l,r]区间内所有值+z的结果

#include <iostream>
using namespace std;
int main()
{
    int n = 0, p = 0, i = 0, j = 0;
    int x = 0, y = 0, z = 0;
    cin >> n >> p;
    for (i = 1; i <= n; i++)
    {
	    cin >> arr[i];
	    brr[i] = arr[i] - arr[i - 1];
    }
	
    cin >> x >> y >> z;
    brr[x] += z;
	brr[y + 1] -= z;

    for (i = 1; i <= n; i++) {
	    arr[i] = brr[i] + arr[i - 1];
    }
	return 0;
}

一维差分练习

洛谷P2367 语文成绩

二维:

二维稍微复杂一些,配图片更容易理解,但是这里大概率没有(

前缀和:

定义数组arr[][],sum[][]

arr[][]储存输入数据

sum[][]储存arr[][]前缀和

sum[i][j]的意义是左上角为arr[1][1]~右下角为arr[i][j]的矩形区间内数值的前缀和

使用场景:

多次对某一个二维区间内数值求和(仅进行一次求和操作,之后只进行查询操作)

递推sum公式:

把左上角为arr[1][1]~右下角为arr[i][j]的矩形区间分为四个小矩形区间

(便于理解,不对arr数组真的操作)

                区间1>arr[1][1]~右下角为arr[i][j-1]

                区间2>arr[1][1]~右下角为arr[i-1][j]

                区间3>arr[1][1]~右下角为arr[i-1][j-1]

                区间4>arr[i][j]~右下角为arr[i][j],其实就是arr[i][j]

        最大矩形的前缀和=(区间1.左+区间2.上-区间3.左上)前缀和 + 区间4.右下角单个arr[i][j]数值

        sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+arr[i][j]

#include <iostream>
using namespace std;
int arr[121][121];
int sum[121][121];
int main()
{
	int n = 0, i = 0, j = 0, x = 0, y = 0;
	int max = -0x7f7f7f, t = 0;
	cin >> n;
	for (i = 1; i <= n; i++)
		for (j = 1; j <= n; j++)
		{
			cin >> arr[i][j];
			sum[i][j]
				+= sum[i - 1][j]
				+ sum[i][j - 1]
				- sum[i - 1][j - 1]
				+ arr[i][j];
		}
	for (i = 1; i <= n; i++)
		for (j = 1; j <= n; j++)
			for (x = i; x <= n; x++)
				for (y = j; y <= n; y++)
				{
					t = sum[x][y]
						- sum[x][j - 1]
						- sum[i - 1][y]
						+ sum[i - 1][j - 1];
					if (max < t)max = t;
				}
	cout << max;
	return 0;
}

二维前缀和练习:

洛谷P1719最大加权矩阵

差分:

二维前缀和的逆运算

使用场景:

对二维区间内某一数值进行批量操作

初始化差分数组不太方便,可以假想在一个 全0的二维矩阵(设为crr[][]) 上操作,最后将变换后的crr[][]与原arr[][]做矩阵加法

二维差分数组与一维差分数组的相似之处:

(定义原数组arr[N][N],差分数组brr[N][N]])

对二维差分数组操作时,假设对brr[i][j]+z,等同于对arr[i][j]~arr[N][N]所有数+z

使用场景,具体操作:

对某一区间(如左上角arr[i][j]~右下角arr[x][y])内数据全部+z

1>(便于理解,不真的对arr数组进行操作)

把左上角arr[i][j]~右下角arr[N][N]大矩形区间分为四个小矩形区间

                区间1>arr[i][j]~右下角为arr[x][y]

                区间2>arr[i][y+1]~右下角为arr[N][N]

                区间3>arr[x+1][j]~右下角为arr[N][N]

                区间4>arr[x+1][y+1]~右下角为arr[N][N]

        直接对全零矩阵进行操作,不考虑初始化brr, 现在brr也是全0矩阵

(可以认为brr是全零矩阵的差分数组)

        brr[i][j] += z

        brr[i][y+1] -= z

        brr[x+1][j] -= z

        brr[x+1][y+1] += z

2>对brr求前缀和,即二维数组前缀和,得到将全零矩阵进行变换后的新矩阵

3>新矩阵和原矩阵arr相加

#include<iostream>
using namespace std;
int arr[1005][1005];
int main()
{
	int m = 0, n = 0;
	int i = 0, j = 0;
	int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	cin >> n >> m;
	while (m--)
	{
		cin >> x1 >> y1 >> x2 >> y2;
		arr[x1][y1]++;
		arr[x1][y2 + 1]--;
		arr[x2 + 1][y1]--;
		arr[x2 + 1][y2 + 1]++;
	}
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			arr[i][j] = arr[i][j] + arr[i - 1][j] + arr[i][j - 1] - arr[i - 1][j - 1];
			cout << arr[i][j] << ' ';
		}
		cout << endl;
	}
	return 0;
}

二维差分练习:

P3397 地毯

<think>好的,我现在需要帮助用户理解C++中的前缀和差分算法,包括它们的实现。首先,我需要回顾一下用户提供的引用内容,确保准确无误地整合信息。 用户引用了四个资料,主要介绍了前缀和差分的基本概念、一维二维的实现方法,以及它们如何优化时间复杂度。根据这些资料,我需要分步骤解释这两个概念,并给出代码示例。 首先,前缀和的定义是什么?根据引用[2]和[3],前缀和是将前i项的和存储起来,方便快速计算区间和。这样可以使得原本需要O(n)的区间求和操作变成O(1)。需要举一个一维前缀和的例子,比如数组num的前缀和数组sum,sum[i] = sum[i-1] + num[i]。然后给出代码,像引用[2]中的代码,使用动态数组分配,并计算前缀和,处理查询时用sum[r] - sum[l-1]。 接下来是差分差分前缀和的逆运算。引用[1]和[3]提到,差分数组b的构建方式是b[i] = a[i] - a[i-1],这样对差分数组的操作可以快速影响原数组。当需要对区间[l, r]加上c时,只需在b[l]加c,b[r+1]减c,然后通过前缀和恢复原数组。这可以将区间修改的时间复杂度从O(n)降到O(1)。需要展示差分的构建和操作步骤,并给出代码示例,如引用[1]中的代码。 然后,可能需要提到二维的情况,但用户的问题主要集中在一维,所以可以简要说明二维的扩展,但重点放在一维。不过引用的资料中有提到二维差分,所以需要确认是否需要包含。用户的问题是关于C++中的前缀和差分,所以应该覆盖基本的一维情况,可能稍微提及二维,但根据用户提供的示例,用户可能只需要一维的解释。 接下来需要检查代码的正确性。例如,前缀和代码中,sum数组的索引是否从1开始,这样处理起来更方便,避免处理边界情况。差分代码中,构建差分数组时,是否从i=1开始,并且如何处理初始数组和差分数组的关系。 然后,要比较传统做法和优化后的差异,比如引用[4]提到的朴素做法是O(n)时间,而差分前缀和优化后是O(1)或O(1)的修改加上O(n)的恢复,但总体在处理多次区间操作时更高效。 需要确保回答结构清晰,分点说明前缀和差分的定义、作用、实现步骤、代码示例以及应用场景。同时,按照用户的要求,生成相关问题。例如,可能的问题包括前缀和差分的区别,二维如何实现,时间复杂度的分析,以及实际应用中的例子等。 需要确保所有数学公式使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$。例如,前缀和的区间和公式是$sum[r] - sum[l-1]$,差分的构建公式是$b[i] = a[i] - a[i-1]$。 最后,引用正确的参考文献,用户提供的引用[1]到[4]需要对应到正确的部分。例如,当提到差分数组的构建时,引用[1]和[3],前缀和的实现引用[2]和[4]。 可能需要注意用户提供的引用中有一些重复的内容,比如引用[1]和[2]都提到了前缀和差分的代码,需要整合这些信息,避免重复。同时,确保代码示例正确,并且解释清楚每部分代码的作用,比如在差分代码中,构建差分数组后,如何通过修改差分数组的l和r+1位置来实现区间更新,再通过前缀和得到结果。 现在需要组织这些内容,先介绍前缀和,再差分,分别给出定义、实现步骤、代码和优缺点。最后总结它们的应用场景,例如前缀和用于频繁区间查询,差分用于频繁区间修改。 可能还需要指出,差分通常和前缀和结合使用,先进行差分操作,再通过前缀和得到修改后的数组。例如,引用[1]中的代码在修改差分数组后,通过循环计算前缀和得到最终的数组。 需要确保回答使用中文,并且语言清晰易懂,结构合理。最后生成的相关问题要围绕这两个概念展开,比如如何优化区间操作,二维实现,与其他算法的比较等。</think>### C++前缀和差分的概念及实现 #### 一、前缀和 **定义**: 前缀和是将数组前$i$项的和预先计算并存储,用于快速计算区间$[l, r]$的和。通过预处理时间复杂度为$O(n)$,查询时仅需$O(1)$[^2][^4]。 **实现步骤**: 1. 初始化前缀和数组`sum`,其中`sum[i] = sum[i-1] + num[i]`。 2. 查询区间和时,直接使用公式$sum[r] - sum[l-1]$。 **代码示例**(一维前缀和): ```cpp #include <iostream> using namespace std; int main() { int n, m; cin >> n >> m; int *num = new int[n + 1]; // 原数组 int *sum = new int[n + 1]; // 前缀和数组 for (int i = 1; i <= n; i++) cin >> num[i]; for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + num[i]; // 计算前缀和 while (m--) { int l, r; cin >> l >> r; cout << sum[r] - sum[l - 1] << endl; // 查询区间和 } return 0; } ``` #### 二、差分 **定义**: 差分前缀和的逆运算,通过维护差分数组$b$,使得原数组$a$满足$a[i] = b[1] + b[2] + \dots + b[i]$。差分可将区间修改操作优化至$O(1)$时间复杂度[^1][^3]。 **实现步骤**: 1. **构建差分数组**:$b[i] = a[i] - a[i-1]$。 2. **区间修改**:将区间$[l, r]$每个元素加$c$时,只需执行: - `b[l] += c` - `b[r + 1] -= c` 3. **还原数组**:通过前缀和运算得到修改后的原数组。 **代码示例**(一维差分): ```cpp #include <iostream> using namespace std; const int N = 1e5 + 10; int a[N], b[N]; // a为原数组,b为差分数组 int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); b[i] = a[i] - a[i - 1]; // 构建差分数组 } int l, r, c; while (m--) { scanf("%d%d%d", &l, &r, &c); b[l] += c; // 左边界加c b[r + 1] -= c; // 右边界后一位减c } for (int i = 1; i <= n; i++) { a[i] = b[i] + a[i - 1]; // 前缀和还原数组 printf("%d ", a[i]); } return 0; } ``` #### 三、对比与优势 1. **前缀和**:适用于**频繁查询区间和**的场景(如统计子数组和)。 2. **差分**:适用于**频繁区间修改**的场景(如批量增减操作)[^1]。 3. **时间复杂度**: - 传统遍历:修改$O(n)$,查询$O(n)$。 - 差分+前缀和:修改$O(1)$,还原$O(n)$;查询$O(1)$。 #### 四、应用场景 - **前缀和**:数据统计、滑动窗口、图像处理中的积分图。 - **差分**:时间序列分析、区间资源分配、二维矩阵区域更新(需扩展至二维差分)[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值