【真香笔记இ௰இ】前缀和、差分、二分模板

文章开头第一句:回家旅游一时爽,集训补课火葬场。
由于博主念家以及贪玩,翘了整个暑假的集训,回来还有一个星期就要比赛集训队成员,万万没想到的是综合考评中集训考勤居然占百分之25,唉,眼看就要退出ACM了,我还是决定临死挣扎一下,把暑假集训的知识点补完,也算给大学时期ACM这件事来个善始善终吧。
Day1-前缀和、差分、二分答案

1. 前缀和

是什么:前缀和是一种数据预处理手法
为什么:降低时间复杂度
怎么弄:每个节点记录的是此节点之前的所有值的和
例1(一维前缀和):给一数组,给m个询问,询问数组从L到R的区间和。
解:
朴素解法:一个一个加,时间复杂度O(m*n)
前缀和处理:时间复杂度O(n+m),n次操作生成s矩阵,m次操作回答询问,自己写一遍完整代码就懂了
故前缀和处理可以降低时间复杂度。
一维前缀和公式:
处理思想
主体代码:

s[0]=a[0];
 for(int i=1;i<=n;i++){
  s[i]=s[i-1]+a[i];
 }
 for(int i=0;i<=m;i++){
  cin>>l>>r;
  cout<<s[r]-s[l-1]<<endl;
 }

例2(二维前缀和):给一个二维矩阵,再给m次询问,询问给出i,j,求从左上角到点i,j形成的矩阵内数字的和。
解:前缀和处理:
在这里插入图片描述
按照上面两张图,最大的矩形前缀和就等于蓝的矩阵加上绿的矩阵,再减去重叠面积,最后加上小方块,即二维前缀和公式:

s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];

主体代码:

//前缀和处理
s[0][0]=a[0][0];
 for(int k=1;k<=n;k++){
  s[0][k]=s[0][k-1]+a[0][k];
  s[k][0]=s[k-1][0]+a[k][0];
  }
 for(int k=1;k<=n;k++){
  for(int p=1;p<=n;p++){
   s[k][p]=s[k-1][p]+s[k][p-1]-a[k-1][p-1]+a[k][p];
  }
 }
 //处理询问
 int m;
 cin>>m;
 for(int k=0;k<=m;k++){
  cin>>i>>j;
  cout<<s[i][j]<<endl;
 }

例3(二维前缀和):给一个二维矩阵,再给m次询问,询问给出两个点坐标x1,y1,x2,y2,求这俩点形成的矩阵内数字的和。
在这里插入图片描述
即求红色圈圈部分,此题与上题的差别就在于处理询问时是这样的:

cout<<s[x2][y2]-s[x1][y2]-s[x2][y1]+s[x1][y1]

关于前缀和,为了便于理解,贴一个用前缀和生成的s矩阵:
在这里插入图片描述

2. 差分

是什么:是一种区间批处理方法
为什么:降低时间复杂度
怎么弄:新开一个数组记录区间操作
例4(一维差分):在一维前缀和例题1的基础上,给你一串长度为n的数列a1,a2,a3…an,要求对a数组进行m次操作,操作分为两种,
操作一:将a[L]~a[R]内的元素都加上P
操作二:将a[L]~a[R]内的元素都减去P
最后再给出一个询问求a[L]-a[R]内的元素之和?
解:
朴素算法:一个一个加了再求前缀和处理求和
差分算法:前缀和处理之前,另开一个数组b这样记录操作:

for(int i=0;i<=m;i++){
  cin>>l>>r>>p;
  b[l]+=p;
  b[r+1]-=p; 	//注意为什么b[r+1]要减p
 }

然后前缀和生成数组s:

s[0]=a[0];
 for(int i=1;i<=n;i++){
  s[i]=s[i-1]+a[i]+b[i];
 }

例5(二维差分)在一维差分例题3的基础上改成:操作分别是在矩阵的某一x1y1到x2y2形成的矩阵区间内加p或减p,最后询问区间值,如图为区间示意图:
在这里插入图片描述
解:操作还和例3是一样的,只不过b数组是个二维数组,并且是这样记录操作的:

 b[x1][y1]+=p;
 b[x2+1][y2+1]-=p;

接着,前缀和生成s数组时公式是这样的:

s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]+b[i][j];
  1. 二分
    每次二分都想不明白l和r的界限,还有越界怎么处理,临场一懵逼更想不出
    学长在pdf里分享了一个二分完美模板,以后直接用就是了
//这个二分模板的作用:
//找到的话且这个数唯一则返回下标位置,如果不唯一则返回最右边的下标;
//找不到的话返回比它大的第一个数(即右边的数)的下标。 
//e为要找的数
 int bSearch(int begin, int end, int e){  
     int mid, left = begin, right = end;  
     while(left <= right)  {  
         mid = (left + right) >> 1;  
         if(arr[mid] >= e) right = mid - 1; 
         else left = mid + 1; 
     }  
     return left;  	//找不到返回右边下标
 }
 //这个模板经过细微的修改可以达到不同的效果,
 //比如越界判断e是否比最大的数还要大,比如找不到就返回左边下表等等

今天笔记完成,还是蛮简单好理解的哈哈所以有一点偷懒,明天计划完成两次课的笔记~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值