手撕算法 ——前缀和

        前缀和与差分的核⼼思想是预处理,可以在暴⼒枚举的过程中,快速给出查询的结果,从⽽优化时间 复杂度。

        是经典的⽤空间替换时间的做法。

一、⼀维前缀和

题⽬来源:⽜客⽹

题⽬链接:【模板】前缀和

难度系数:★

1. 题目描述 

2. 算法原理

解法一:暴力解法-->模拟(时间复杂度过大,超时

通过for循环模拟相加,最多 n * q

解法二:前缀和-->快速求出数组中模拟区间的和

  1. 创建前缀和数组f [i] = f [i 1] + a[i]

     2.查询[l, r]区间和:f [r] f [l 1]

注意使用前缀和数组时,建议数组遍历从数组下标位 1 的位置开始,不会出现a[-1],处理边界情况。

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n, q;
LL a[N];
LL f[N]; // 前缀和数组

int main()
{
    cin >> n >> q;
    
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    // 处理前缀和数组
    for(int i = 1; i <= n; i++)
    {
        f[i] = f[i - 1] + a[i];
    }
    
    // 处理 q 次询问
    while(q--)
    {
        int l, r; cin >> l >> r;
        cout << f[r] - f[l - 1] << endl;
    }
    
    return 0;
}

二、最大子段和

题⽬来源:⽜客⽹

题⽬链接:P1115 最大子段和 - 洛谷

难度系数:★

1. 题目描述 

2. 算法原理

考虑以 i 位置的元素 a[i]「为结尾」的最⼤⼦段和:

  • 在求「区间和」时,相当于是⽤ f[i] 减去 i 位置前⾯的某⼀个 f[x]
  • 如果想要「最⼤⼦段和」,也就是「最⼤区间和」,那么⽤ f[i] 减掉⼀个「前驱最⼩值」即可。

f[i] 的值是确定的,只要减去前面最小的前驱 prevmin  就能得到最大字段和 ret

        因此,我们可以创建 a 数组的「前缀和」数组,然后在遍历前缀和数组的过程中,⼀边「更新前驱最 ⼩值」,⼀边「更新当前位置为结尾的最⼤⼦段和」。

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 2e5 + 10;

int n;
LL f[N]; // 前缀和数组

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        LL x; cin >> x;
        f[i] = f[i - 1] + x;
    }

    LL ret = -1e20;
    LL prevmin = 0;

    for(int i = 1; i <= n; i++)
    {
        //当前情况,当前最大值减去前驱最小值 
        ret = max(ret, f[i] - prevmin);
        //更新下一个位置的前驱最小值
        prevmin = min(prevmin, f[i]);
    }

    cout << ret << endl;

    return 0;
}

三、⼆维前缀和

题⽬来源:⽜客⽹

题⽬链接:【模板】二维前缀和

难度系数:★

1. 题目描述

2. 算法原理

解法一:暴力解法-->模拟

使用for循环遍历相加,时间复杂度过大

解法二:二维前缀和-->快速查询二维数组中某一个子矩阵中所有的元素和

        ⼆维前缀和模板题,直接套⽤「公式」创建前缀和矩阵,然后利⽤前缀和矩阵的「性质」处理 次询 问。

  1. 创建前缀和矩阵f [i][j] = f [i 1][j] + f [i][j 1] f [i 1][j 1] + a[i][j]

 

  1. 查询以(x1 , y1 )为左上⻆ ,(x2 , y2 )为右下⻆的⼦矩阵的和

 

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1010;

int n, m, q;
LL f[N][N];

int main()
{
    cin >> n >> m >> q;
    // 预处理前缀和矩阵
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            LL x; cin >> x;
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + x;
        }
    }
    
    // 处理 q 次查询
    while(q--)
    {
        int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
        cout << f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1] << endl;
    }
    
    return 0;
}

四、激光炸弹

题⽬来源:洛⾕

题⽬链接:P2280 [HNOI2003] 激光炸弹 - 洛谷

难度系数:★★

1. 题目描述 

2. 算法原理 

        可以⽤⼀个⼆维矩阵将所有⽬价值存起来,其中 a[i][j就表⽰ [i, j] 位置的⽬标价值之和。⼀颗炸弹能够得的价值正好是⼀个 R × ⼤⼩的⼀个正⽅形内所有⽬标价值总和,么我可以求出  可。矩阵的前缀和矩阵,然后枚举所有⻓为 R 的⼦正⽅形的价值之和,求出⾥⾯的最⼤即可。

枚举⻓为 R 的所有正⽅形

  • 仅需枚举右下⻆[x2 , y2 ] (R + 1 x25000, R + 1 y2 5000) 么结合 就可算出左上⻆[x2 − R + 1, y2 − R + 1]
  • 代⼊前缀和矩阵中,就可以快速求出个正⽅形内所有⽬总价值。

细节问题

  • 题⽬中⼀个会「重复出现, a[i][j]+ = w
  • 半径 能「5000 ,此时炸弹可以摧毁所有⽬,也就是整个矩阵的⽬标价值之和

3. 参考代码

#include <iostream>
using namespace std;
const int N = 5010;
int n, m;
int a[N][N];
int f[N][N]; // 前缀和矩阵

int main()
{
	cin >> n >> m;
	while (n--)
	{
		int x, y, v; cin >> x >> y >> v;
		x++, y++; // 下标从1开始计数

			a[x][y] += v; // 同⼀个位置有可能有多个⽬标

	}
	n = 5001;
	// 预处理前缀和矩阵

		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j];
			}
		}
	int ret = 0;
	m = min(m, n); // 如果m很⼤,相当于就是把整个区域全部摧毁

		// 枚举所有边⻓为m的正⽅形

		for (int x2 = m; x2 <= n; x2++)
		{
			for (int y2 = m; y2 <= n; y2++)
			{
				int x1 = x2 - m + 1, y1 = y2 - m + 1;
				ret = max(ret, f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 -
					1][y1 - 1]);
			}
		}
	cout << ret << endl;
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值