【差分】【前缀和 & 二维前缀和】详解

差分

内存限制:128 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较

题目描述
输入一个长度为n的整数序列。

接下来输入m个操作,每个操作包含三个整数 l,r,z
表示将序列中l,r之间的每个数加上z 。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数n和m 。

第二行包含n个整数,表示整数序列。
接下来 m行,每行包含三个整数 l,r,z;
表示一个操作。
输出格式
共一行,包含n个整数,表示最终序列。

分析

首先如果这道题用普通方法做是肯定会超时的,这个时候,我们不得不寻求一种简便方法,我们观察题目后可知,每一次操作都会在一个区间内进行,那么我们只要关注这个区间的一头一尾就可以了。

思路

每一次操作记录下他的一头一尾,也就是l,r这是我们在l处记录一个加1,在r+1处记录一个减一,这就表示从l,到r,每个数需要执行l的操作,即满足题目的意思,我们可以用一个l数组来存储,如图:
在这里插入图片描述
这时候我们还需要一个另外的数组来存储每一个元素改变了多少,我们称为g数组,他的计算过程是第一个数就是f数组的和,以后每一个数都用前一个数的值来加上现在这个数的f[]的值,具体如图:
在这里插入图片描述
这时候我们只需要用g数组的值加上对应的一开始的值,就得到了最终序列,直接输出即可。
下面是代码实现:

#include<bits/stdc++.h>
using namespace std;
int m,n,i,l,r,z,a[1000000],f[1000000],g[1000000];
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(i=1;i<=n;i++)
		cin>>a[i];
	for(i=1;i<=m;i++){
		cin>>l>>r>>z;
		f[l]+=z,f[r+1]-=z; 
	}
	g[1]=f[1];//特判 
	cout<<g[1]+a[1]<<" ";//记得加a[1]哟 
	for(i=2;i<=n;i++){
		g[i]=g[i-1]+f[i];
		cout<<g[i]+a[i]<<" ";//记得加a[i]哟
	}
}

怎么样,这个方法是不是很简单呢!

[语言月赛202303] Carrot Harvest G

题目描述

n n n m m m 列共 n × m n \times m n×m 个坑,每个坑可能有一个萝卜,也可能没有。

现在 Farmer John 需要至少拔 k k k 个萝卜,他只能挑一个矩形(长方形或正方形)区域的坑进行拔萝卜。

请你求出,为了至少拔 k k k 个萝卜,他需要挑的矩形面积(坑的数量)最小是多少。

输入格式

输入共 n + 1 n + 1 n+1 行。

第一行为三个整数 n , m , k n, m, k n,m,k

第二行至第 n + 1 n + 1 n+1 行,每行 m m m 个只可能为 0 0 0 1 1 1 的整数。其中第 i + 1 i + 1 i+1 行的第 j j j 个整数为 a i , j a _ {i, j} ai,j,代表第 i i i 行第 j j j 列的坑中是否有萝卜。 a i , j = 1 a _ {i, j} = 1 ai,j=1 代表有萝卜, a i , j = 0 a _ {i, j} = 0 ai,j=0 代表没有萝卜。

输出格式

输出共一行一个整数,代表为了至少拔 k k k 个萝卜,Farmer John 需要挑的矩形的最小面积(坑的数量)。

样例 #1

样例输入 #1

5 5 7
0 0 0 1 0
0 0 1 1 1
0 1 1 1 1
0 1 1 0 0
0 0 0 0 1

样例输出 #1

8

提示

样例 1 解释

如下图所示,绿色底色的方格为有萝卜的区域,白色底色的方格为无萝卜的区域。红色框起的区域为一种拔萝卜的区域,使用 8 8 8 的面积拔了 7 7 7 个萝卜。可以证明不存在工作面积更小的拔萝卜方式。

数据规模与约定

对于 100 % 100\% 100% 的数据,保证 1 ≤ n , m ≤ 20 1 \leq n, m \leq 20 1n,m20 1 ≤ k ≤ 400 1 \leq k \leq 400 1k400

测试点编号 n n n m m m k k k
2 2 2 = 2 = 2 =2 = 2 = 2 =2 = 1 = 1 =1
3 , 4 3, 4 3,4 = 2 = 2 =2 = 2 = 2 =2 ≤ 4 \leq 4 4
5 5 5 ≤ 20 \leq 20 20 = 1 = 1 =1 ≤ 400 \leq 400 400
6 , 7 6, 7 6,7 ≤ 20 \leq 20 20 = 2 = 2 =2 ≤ 400 \leq 400 400
1 , 8 , 9 , 10 1, 8, 9, 10 1,8,9,10 ≤ 20 \leq 20 20 ≤ 20 \leq 20 20 ≤ 400 \leq 400 400

数据保证一定有至少一种拔萝卜的方式可以拔至少 k k k 个萝卜。

B.简单线段笔(Easy version)

题目背景

MS 玩起了几何画板,他把线段填上又擦掉。
魔怔了,加强一下可以变成简单线段树。

Hard version

题目描述

MS 有一条 1 1 1 n n n 的线段。

m + q m+q m+q 次操作。

m m m 次操作,我们将 [ l , r ] [l,r] [l,r] 区间内有线段的地方擦除,没线段的地方画上线段。

q q q 次操作,查询 [ l , r ] [l,r] [l,r] 区间内总共有多少单位长度的线段(区间 [ x , x + 1 ] [x,x+1] [x,x+1] 的长度为一单位长度).

输入格式

第一行输入 n n n 代表线段长度.

第二行输入 m m m q q q

之后 m + q m+q m+q 行,每行输入 l l l r r r

输出格式

输出 q q q 行,每行一个数,代表区间内总共有多少单位长度的线段。

样例 #1

样例输入 #1

8 3 2
1 3
2 8
4 6
1 8
2 7

样例输出 #1

5
5

提示

样例解释

1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times10^5 1n5×105

1 ≤ m , q ≤ 2 × 1 0 6 1\le m,q\le2\times10^6 1m,q2×106

本题I/O量较大,请使用较快的读入方式

分析

本题有两种方法
1.线段树
2.差分
我们在这里主要讲解差分。

差分主要是利用m数组来记录每一个区间的变化量,而不直接修改,每一步在m[l]上+1,在m[r+1]上-1,就能达到记录变化量的效果了。

以前写的博文,可参考:差分详解
代码:

#include <bits/stdc++.h>
using namespace std;
int n, m, q, l, r, w[500005], dp[500005];
bool a[500005];
int main(){
	scanf("%d%d%d", &n, &m, &q);
	while(m--){
		scanf("%d%d", &l, &r);
		w[l]++, w[r + 1]--;
	}
	//记录变化量
	for(int i = 1; i <= n; i++){
		a[i] = (w[i] + a[i - 1]) % 2;
		dp[i] = dp[i - 1] + !a[i];
	}
	//a[i]记录当前点的状态,而dp[i]则记录1-i共有多少的点是true,这样在查询时可以直接减就不用循环了
	while(q--){
		int f = 0;
		scanf("%d%d", &l, &r);
		cout << dp[r] - dp[l - 1] << endl;
	}
}

二维前缀和

根据题意可知,这道题让我们求指定个数最小区间,暴力(TLE)实在是太慢了。
这里就要引出二维前缀和的概念了。
二维前缀和,顾名思义就是一个点的左上方所有点之和。
在这里插入图片描述
当然一个一个的加也很慢,所以我们要递推出公式,我们发现
在这里插入图片描述
红色点的前缀和就=(灰色+蓝色)+(绿色+蓝色)-蓝色,=[5,c]的前缀和+[4,d]的前缀和-[4,c]的前缀和。
进一步得到通项公式

a [ i, j ] = a[ i - 1 ][ j ] + a[ i ] [j - 1] - a[ i - 1 ] [ j - 1 ]

那么,光知道一个点的前缀和还是不够的,还要知道一个点减另一个点的前缀和。
在这里插入图片描述
如图,右下角红色方格-左上角红色方格的前缀和=右下角红色方格前缀和-[9,a]前缀和-[3,e]前缀和+左上角红色方格前缀和。
得到公式:

a[ i ] [ j ] - a[ x ] [ y ] = a [ i ] [ j ] - a [ i ] [ y ] - a [ x ] [ j ] + a [ x ] [ y ]

有了这两个公式后,我们就可以轻松解决这道题了!


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

来自八中的小鹿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值