前缀和(模板+例题)

利用前缀和,我们可以快速算出某段区间的和。不过前缀和只能处理静态数组,只用于查找,不修改

一、模板

1.一维前缀和

假设有一个数组[L,R],如果一个个算某区间和

S1 = a1

S2 = a1 + a2

...(会有很多不必要的计算),前缀和有点像递推,像如下公式这样计算

Si = Si-1 + ai;

然后如果要找到某一段的区间和

aL + aL+1 + ... +aR =SR - SL-1

下面这段代码大概意思是输入一个长度为n的数组,有m个样例,每个样例给出L,R,输出这个样例的区间和

一维前缀和 
#include <iostream>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;

const int N = 100010;
int n, m;
int a[N];// 表示原数组 
int s[N];// 表示前缀和数组 

int main() 
{
	scanf("%d%d", &n, &m);
	for(int i = 1;i <= n;i++)
	{
		scanf("%d", &a[i]);
		s[i] = s[i - 1] + a[i];
	}
	while(m --)
	{
		int l, r;
		scanf("%d%d",&l,&r);
		printf("%d\n",s[r] - s[l - r]);
	}
    return 0;
}

2.二维前缀和

 

y1y2
1724
x13628
x22123

计算前缀和矩阵

利用容斥原理

Sx,y = Sx-1,y + Sx,y-1 + Sx-1,y-1 + ax,y (Sx-1,y-1减了两次,加回来一次)

计算某子矩阵和(蓝色数字)

S[x1,y1~x2,y2] = Sx2,y2 - Sx2,y1-1 - Sx1-1,y2 + Sx1-1,y1-1

#include <iostream>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;

const int N = 1010; 
int n, m, q;
int a[N][N],s[N][N];

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <=m; j ++)
		{
			scanf("%d",a[i][j]);
			s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
		}
		
	while(q --)
	{
		int x1, y1, x2, y2;
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		printf("%d\n",s[x2][y2] - s[x1 - 1][y2] -s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
	}	
	return 0;
}

 二、例题

1.激光炸弹

地图上有 N 个目标点,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形包含的横纵位置数量,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤109
0<N≤10000
0≤Xi,Yi≤50000
0≤Wi≤1000

输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
#include <iostream>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;

const int N = 5010;
int n, m;
int s[N][N];//防止mle只开一个数组 

int main()
{
	int cnt, r;
	cin >> cnt >> r;
	r = min(5001, r);
	
	n = m = r;
	while(cnt --) 
	{
		int x, y, w;
		cin >> x >> y >> w;
		x ++, y ++;
		n = max(n, x), m = max(m, y);
		s[x][y] += w;
	}
	
	for(int i = 1; i <=n; i ++) //预处理前缀和数组 
		for(int j = 1; j <=m; j ++)
			s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1]; 
	
	int res = 0;
	//枚举所有边长是r的矩形,枚举(i,j)为右下角
	for(int i = r; i <= n; i ++)
		for(int j = r; j <= m; j ++)
			res = max(res,s[i][j] - s[i-r][j] - s[i][j-r] + s[i-r][j-r]); 

	cout << res <<endl;
    return 0;
}

注意:目标是坐标轴上的点,不是方块。R * R框出的矩形,边长上的点不算,所以最大能框住点的矩形不是和坐标轴重合,而是错开的,比如这个矩形的顶点在坐标轴中间,不在坐标轴上,这样可以得到最多的目标。

2.k倍区间

给定一个长度为 NN 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式

输出一个整数,代表 K 倍区间的数目。

数据范围

1≤N,K≤100000,
1≤Ai≤100000

输入样例:
5 2
1
2
3
4
5
输出样例:
6

首先想到暴力,但是时间复杂度O(n^3),需要优化

for(int r = 1; r <= n; r ++)
    for(int l = 1; l <= r; l ++)
    {
        int sum = 0;
        for(int i = l; i <= r; i ++)
            sum += a[i];
        if(sum % k == 0) ans ++;
    }

 然后想到用前缀和优化一下,时间复杂度O(n^2),还是比较高,过不了所有

for(int r = 1; r <= n; r ++)
    for(int l = 1; l <= r; l ++)
    {
        int sum = s[r] - s[l - 1];
        
        if(sum % k == 0) ans ++;
    }

因此我们思考

第二层循环的作用是枚举左端点,即(s[r] - s[0, r - 1]) % k = 0,当这个条件成立答案就加一

化简这个式子得到 s[r] % k = s[l − 1] % k

然后我们用空间换时间,开一个cnt[ i ] 数组,表示余数 为 i 的数有几个

用 cnt[ s[i] %k ] 得到前面有无和该前缀和余数相同的前缀和,相同res++

然后注意,余数为0已经满足条件,没有 0 % k == 0 这项,所以要先给 cnt[0] 赋 1

ps:看不懂可以拿样例一点点模拟一遍就能够明白这个过程了(我就是这么懂的)

#include <iostream>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;

typedef long long ll;
const int N = 100010;
int n, k;
ll s[N],cnt[N];

int main()
{
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n; i ++)
	{
		scanf("%lld",&s[i]);
		s[i] += s[i - 1];
	}
	
	ll res = 0;
	cnt[0] = 1;
	for(int i = 1;i <= n; i ++)
	{
		res += cnt[s[i] % k];
		cnt[s[i] % k] ++;
	}
	
	printf("%lld\n",res);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值