首页 > 其他分享 >【模版】差分

【模版】差分

时间:2023-12-22 10:23:21浏览次数:229  
标签:int 模版 差分 修改 数组 y1 y2

问题引入:洛谷P2367

班上一共n个学生,语文老师需要对成绩进行p次修改,每次修改需要给第x个学生到第y个学生每个人增加z分,语文老师想知道修改成绩后的最低分。

对于 $40\%$ 的数据,有 $n \le 10^3$。

对于 $60\%$ 的数据,有 $n \le 10^4$。

对于 $80\%$ 的数据,有 $n \le 10^5$。

对于 $100\%$ 的数据,有 $n \le 5\times 10^6$,$p \le n$,学生初始成绩 $ \le 100$,$z \le 100$。

 

分析:直接模拟修改的时间复杂度为O(n2),肯定无法通过本题

一维差分的定义:对于这个原数组 a[ ] = {a1,a2,a3,···,an},我们构造出这样一个数组 B[ ] = {b1,b2,b3,···,bn},使得 ai = b1 + b2 + ··· + bi,那么b[ ] 就称为 a[ ] 的差分,a[ ] 称为 b[ ] 的前缀和。其中b1 = a1,b[i] = a[i] - a[i-1] (2<=i<=n),可以发现,差分与前缀和是一组逆运算。

 如何利用差分数组对区间进行修改呢?为什么利用差分数组能提升修改的效率呢?

  • 1.区间修改,时间复杂度为 O(1)

​ 现在要将原数组 a[ ] 区间【L,R】上的每个数都加上一个 c,如下图所示:

  • 第一个受到影响的差分数组中的元素为 bL],所以 b[L] += c ,对于 a[L] 后面的数都会受到 B[L] 的影响加上 c。
  • 最后一个受影响的差分数组中的元素b[R],为了保证不影响到 R 后面的元素,所以我们需要 b[R + 1] -= c。

也就是说,对于 a[x] = b[1] + b[2] + ···+ b[x],利用差分数组能够精确地实现只修改指定区间内元素的目的,而不会修改区间外的a[ ] 的值,也就是:

void insert(int l,int r,int c)
{
    b[l] += c;
    b[r+1] -= c;
}

(1) 1 ≤ x < L, 前缀和 a[x] 不变。
(2) L ≤ x ≤ R, 前缀和 a[x] 加上了 c 。
(3) R < x ≤ N, 前缀和 a[x] 不变,因为被 b[R + 1] 中的c抵消了。
这样一来,就不必每次都对区间内所有的数进行处理,只需要修改区间【L,R】的两个端点 b[ ] 的值即可,复杂度是 O(1) 的。

本题AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 5000010
int n,m,x,y,z,a[MAXN],b[MAXN];
int main() {
    cin >>n>>m;
    for (int i=1;i<=n;i++) {
        cin >>a[i];
        b[i] = a[i]-a[i-1];   //求差分数组,也可以insert(i,i,a[i])
    }
    for (int i=1;i<=n;i++) {
        cin >>x>>y>>z;
        b[x]+=z;
        b[y+1]-=z;       //修改操作
    }
    int min = 101;
    for (int i=1;i<=n;i++) {
        b[i] += b[i-1];   //求前缀和
        if(b[i]<min) min = b[i];
    }
    cout << min;
    
    return 0;
}

差分的局限性:我们可以注意到,利用差分数组 b[] 可以将原来O ( n ) 的区间修改,降为O ( 1 ) 的端点修改,从而提高了修改操作的效率。

但是,对于一次的查询操作,我们必须计算前缀和 b[1] + b[2] + ··· + b[x]才能将原数组 a[x] 求出,计算量是 O ( n )的,即一次查询的复杂度是 O ( n ) 的。也就是说,如果查询操作发生多次,例如 m 次修改,k 次查询,且修改和查询的顺序是随机的,即可能边修改边查询。此时总复杂度为:m 次修改复杂度 O ( m ),k次查询复杂度 O ( k n ),即 o ( m + k n ) 。还不如直接暴力来的快 O ( m n + k )。

可以看出,尽管差分数组对于 ”区间修改“很高效,但是对于”单点查询“来说略显吃力。此时有专门的数据结构来解决这一类问题:树状数组和线段树。

 

 二维差分:

有了一维差分的认识后,我们容易就能拓展到二维差分。一维是线性的,一段区间【L,R】有两个端点;二维是一个矩阵,一块区间由四个端点所围成。

问题描述: 在 n × n 的格子上有 m 个地毯。给出这些地毯的信息,问每个点被多少地毯覆盖。

输入: 第一行是两个整数n, m。接下来 m 行,每行 2 个坐标(x1, y1) 和 (x2, y2 ),代表一块地毯,左上角是 (x1, y1),右下角是(x2, y2)。

输出:输出n行,每行n个正整数。第i行第j列的正整数表示(i, j)这个格式被多少地毯覆盖。

同前面一样考虑朴素算法,每次修改时间复杂度为O(n2),共 m 次,所以时间复杂度为O(m+n2),肯定超时

(1)二维差分的定义

 在一维差分中,原数组a[ ]是从第1个b[1]开始的差分数组 b[ ]的前缀和:a[x]= b[1] + b[2] + ··· + b[x]。
 在二维差分中,a[ ][ ]是差分数组b[ ][ ]的前缀和,即将原点坐标(1,1)和坐标(i,j)围成的矩阵中,所有的b[ ][ ]相加等于a[ i ][ j ]。我们可以把每个b[][]看成一个小格;在坐标(1,1)和(i,j)所围成的范围内,所有小格子加起来的总面积,等于 a[i][j]。如下图中,每个格子的面积是一个 b[ ][ ],例如阴影格子是b[ i ][ j ],它由4个坐标点组成:( i , j )、( i − 1 , j )、( i , j − 1 ) 、( i − 1 , j − 1 ) 。坐标点(i, j)的值是 a[ i ][ j ],它等于坐标(1,1)和(i,j)所围成的所有格子的总面积 。

 由上图我们可以得到二维差分的定义:在二维情况下,差分就变成了相邻a[][]的"面积差",计算公式是:

b [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] 

即利用上图红色大面积 a [ i ] [ j ] 减去两个小面积 a [ i − 1 ] [ j ] 、 a [ i ] [ j ] ,由于两个小面积公共的部分a[i-1][j -1]被减去了 2 次,故要加回来 1 次 a [ i − 1 ] [ j − 1 ] 。

 

两个端点的修改操作如下:

b[x1][y1] += c; //  二维区间的起点
b[x1][y2 + 1] -= c; // 把 x看成常数,y从 y1 到 y2
b[x2 + 1][y1] -= c;// 把 y看成常熟,x从 x1 到 x2
b[x2 + 1][y2 + 1] += c;// 由于前面两式把 c 减去了 2 次,故要加回 1 次

本题AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 1010
int n,m,x1,x2,y1,y2;
int a[MAXN][MAXN];

int main() {
    cin >>n>>m;
    for (int i=1;i<=m;i++) {
        cin >>x1>>y1>>x2>>y2;
        
        a[x1][y1]++;     //修改操作
        a[x1][y2+1]--;
        a[x2+1][y1]--;
        a[x2+1][y2+1]++
    }
    for (int i=1;i<=n;i++,printf('\n'))
        for (int j=1,j<=n;j++) {
            a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1]; //求前缀和
            cout << a[i][j];
        }
    return 0;
}

 

引用:

【详解】手撕 一维、二维、三维差分数组原理(附图解,模板,例题分析)-CSDN博客

标签:int,模版,差分,修改,数组,y1,y2
From: https://www.cnblogs.com/Yukie/p/17920708.html

相关文章

  • 离散化,前缀和,差分
    离散化,前缀和,差分一维前缀和和差分之前学过不再记录二维情况前缀和多维前缀和的普通求解方法几乎都是基于容斥原理例如有这样一个矩阵,可以视为二维数组:124351246359定义一个矩阵\(sum\)使得\(sum_{x,y}=\sum_{i=1}^{x}\sum_{j=1}^{y}a_{i,j}\)那么这个矩阵......
  • 差分
    P3397地毯syoj1829.地毯二维差分板子。#include<bits/stdc++.h>usingnamespacestd;constintN=1010;intn,m;inta[N][N],s[N][N];intmain(){ scanf("%d%d",&n,&m); while(m--){ intx,y,xx,yy; scanf("%d%d%d%d",&x,&y,......
  • 【模版】前缀和
    问题引入:【洛谷P8218】##题目描述给定$n$个正整数组成的数列$a_1,a_2,\cdots,a_n$和$m$个区间$[l_i,r_i]$,分别求这$m$个区间的区间和。对于所有测试数据,$n,m\le10^5,a_i\le10^4$ 最朴素的想法,就是对于每次询问,我们都用for循环进行$[l_i,r_i]$区间的求和,不难......
  • P5091 【模版】扩展欧拉定理
    求\(a^b\bmodm,b\le10^{200000}\)。首先引入三种可以通过取模缩小幂指数的方法。费马小定理:当\(a,p\in\mathbb{Z},\spacep\)为质数且\(p\nmida\)时,\(a^{p-1}\equiv1(\bmod\spacep)\),所以有\(a^b\equiva^{b\bmod(p-1)}(\bmod\spacep)\);欧拉定理:当\(a,m\in\m......
  • 【模版】冒泡排序
    刚学C++时书上就会写这个qwq属于最简单的排序算法惹。算法步骤比较相邻的元素。如果第一个比第二个大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后一个。持续每次对......
  • 【模版】选择排序
    选择排序(Selectionsort)是一种简单直观的排序算法。1.基本思想首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。选择排序的思想其实和冒泡排序有点类似,都......
  • 【模版】计数排序
    引入:P1271【深基9.例1】选举学生会在实际中,一般会在投票区放n个投票箱,投完后只需要计数每个投票箱即可。就此可引入计数排序。本题AC代码(虽然这题直接sort就行了...)#include<iostream>usingnamespacestd;inta[1010]={0},n,m,tmp;intmain(){cin>>n>>m;for......
  • 【模版】归并排序
    归并排序,它有两大核心操作.一个是将数组一分为二,一个无序的数组成为两个数组。另外一个操作就是,合二为一,将两个有序数组合并成为一个有序数组。时间复杂度情况:最好和最快情况都是:O(NlogN)代码模版如下intarr[N],temp[N];voidmerge_sort(intarr[],intl,intr......
  • 【模版】快速排序
    快速排序基本思想通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。算法复杂度最差时间复杂度O(N2)平均时间复杂度O(Nl......
  • 【算法模版】二分查找
    1.简介故事分享......