11.9&&11.10前缀和

11/9

每日一题:3242.设计相邻元素求和服务

思路:偏移量法

用一个长8的数组存放偏移量,前四个为上下左右,后四个为斜向。

用一个大小为2n^2​的数组s预处理元素和。s[v][0] 为adjacentSum(v)的结果,s[v][1]为diagonalSum(v)的结果。

将grid重新resize为(n+2)*(n + 2)的方阵egrid,在四周加一圈0,这样无需判断下标越界。

在遍历egrid过程中,遍历偏移向量,累加每个元素把四个方向的和计算出来。

class NeighborSum {
  static constexpr int dirs[8][2] = {{-1 , 0 }, {1 , 0} , {0 , -1} , {0 , 1} , {-1 , 1} , { -1 , -1} , {1 , 1} , { 1 , -1}};
  vector<array<int , 2>> s;
public:
    NeighborSum(vector<vector<int>>& grid) {
     int n = grid.size();
     vector<vector<int>> egrid(n + 2 , vector<int>(n + 2 , 0));
     for (int i = 1; i <= n ; i++) {
        for (int j = 1; j <= n; j++) {
           egrid[i][j] = grid[i - 1][j - 1];
        }
      }
    
    s.resize((n + 2) * (n + 2));
    for (int i = 1; i <= n; i++) {
       for (int j = 1; j <= n ; j++) {
          int v = egrid[i][j];
          for (int k = 0 ; k < 8 ; k ++){
            int x = i + dirs[k][0] , y = j + dirs[k][1];
            s[v][k / 4] += egrid[x][y];
          }
       }
    }
    }
    
    int adjacentSum(int value) {
          return s[value][0];
    }
    
    int diagonalSum(int value) {
        return s[value][1];    
    }
};

/**
 * Your NeighborSum object will be instantiated and called as such:
 * NeighborSum* obj = new NeighborSum(grid);
 * int param_1 = obj->adjacentSum(value);
 * int param_2 = obj->diagonalSum(value);
 */

58. 区间和

思路:前缀和

最后输出的应该是区间[a,b](左闭右闭)的和,也即sum[b] - sum[a - 1]。因此需要讨论a是否为0。

cout << 这里的三目运算符需要加括号。

#include<iostream>
#include<vector>
using namespace std;

int main(){
  int s = 0;
  int n , a , b;
  cin >> n;
  vector<int> sum(n);
  vector<int> array(n);
  for (int i = 0; i < n; i++) {
      scanf("%d" , &array[i]);
      s += array[i];
      sum[i] = s;
  }
  
  while(cin >> a >> b){
    cout << (a == 0? sum[b] : sum[b] - sum[a - 1]) << endl;
  }
  return 0;
}

44. 开发商购买土地

思路:模拟即可,在暴力\(o(n^3)\)的解法上优化一下即可。

输入的时候算出总和,然后分别对按行切和列切计算res ,找sum - cntcnt的最小差值。

在行向遍历的时候,遇到行末尾就统计一下, 在列向遍历的时候,遇到列末尾就统计一下。

#include <iostream>
#include <vector>
#include <climits>

using namespace std;
int main () {
    int n, m;
    cin >> n >> m;
    int sum = 0;
    vector<vector<int>> vec(n, vector<int>(m, 0)) ;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> vec[i][j];
            sum += vec[i][j];//输入的时候算出总和
        }
    }

    int result = INT_MAX;
    int count = 0; // 统计遍历过的行
    for (int i = 0; i < n; i++) {
        for (int j = 0 ; j < m; j++) {
            count += vec[i][j];
            // 遍历到行末尾时候开始统计
            if (j == m - 1) result = min (result, abs(sum - count - count));
        }
    }

    count = 0; // 统计遍历过的列
    for (int j = 0; j < m; j++) {
        for (int i = 0 ; i < n; i++) {
            count += vec[i][j];
            // 遍历到列末尾的时候开始统计
            if (i == n - 1) result = min (result, abs(sum - count - count));
        }
    }
    cout << result << endl;
}

ACWING795.前缀和

题目中说的l、r分别是第几个数,对应数组下标应该+1,因此从1开始遍历存进去。最后输出s[r] - s[l - 1]即可。

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N] , s[N];

int main(){
  int n , m;
  cin >> n  >> m;

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

796.子矩阵的和

思路:二维前缀和

前缀和的定义:s[i][j]的定义是,以(0,0)为左上顶点,[i][[j]为右下顶点围成的矩形内的所有数之和。如图:

image-20241109212842972

前缀和的计算公式:利用周围的点和该坐标点

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

image-20241109213001398

求子矩阵和:利用对角线的点及其左(上)的点

Sum(矩阵和) = s[x2][y2] - s[x2][y1- 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]

image-20241109213236563

#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N][N] , s[N][N];

int main(){
  int n , m , q;
  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];
      }
  }

  int x1 , x2 , y1 , y2;
  while(q --){
    cin >> x1 >> y1 >> x2>> y2; 
    cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
  }
  return 0;
}

797.差分

思路:一维差分

类似于数学中的求导和积分,差分可以看成前缀和的逆运算。

差分数组:

首先给定一个原数组aa[1], a[2], a[3],,,,,, a[n];

然后我们构造一个数组bb[1] ,b[2] , b[3],,,,,, b[i];

使得 a[i] = b[1] + b[2 ]+ b[3] +,,,,,, + b[i]

也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。

如何构造差分b数组?

image-20241110005422460

 s[i] = a[i] - a[i - 1];//构建差分数组

b数组的b[i]的修改,会影响到a数组中从a[i]及往后的每一个数。

一维差分结论:给a数组中的[ l, r]区间中的每一个数都加上c,只需对差分数组bb[l] + = c, b[r+1] - = c

我们画个图理解一下这个公式的由来:

29688_7bd462bed2-20201215163431253

b[l] + c,效果使得a数组中 a[l]及以后的数都加上了c(红色部分),但我们只要求lr区间加上c, 因此还需要执行 b[r+1] - c,让a数组中a[r+1]及往后的区间再减去c(绿色部分),这样对于a[r] 以后区间的数相当于没有发生改变。

   cin >> l >> r >> c;
    s[l] += c;
    s[r + 1] -= c;

最后输出的时候要注意利用差分数组计算前缀和输出。

   for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i - 1];    //前缀和运算
        printf("%d ", a[i]);
    }
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N] , s[N];

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

798.差分矩阵

思路:二维差分

构造前缀和矩阵时:b[i][j] = b[i-1][j] + b[i][j-1 ] - b[i-1][ j-1] + a[i] [j]

转换后构造二维差分的公式为:a[i][j] = b[i][j] - b[i-1][j] - b[i][j-1] + b[i-1][j-1];

如何在求出的二维差分数组上加上一个c ,使它的原二维前缀和数组的子矩阵里的所有元素都加上一个c ?

image-20241110163510828

即为:

b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N][N] , b[N][N];//设a[][]为原矩阵,b[][]为差分矩阵

int main(){
  int n , m , q , x1 , y1 , x2 , y2 , c;
  cin >> n >> m >> q;
  for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
          cin >> a[i][j];
           //构造差分矩阵
           b[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1];
      }
  }

  while(q --){
    cin >> x1 >> y1 >> x2 >> y2 >> c;
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
  }

 for (int i = 1; i <= n; i++) {
      for (int j = 1; j <= m; j++) {
          a[i][j] = b[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
          cout << a[i][j] << " ";
      }
      cout << endl;
 }
  return 0;
}

补充:也可以构造insert函数直接构造差分数组,如下:

void insert(int x1 ,int y1 ,int x2 ,int y2 ,int c){
  b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

相当于想象a[i][j]为空,每次插入数a[i][j],这个循环过程中就构造了差分数组

for (int i = 1; i <= n; i++)    
{         for (int j = 1; j <= m; j++)        
       {            
             insert(i, j, i, j, a[i][j]);      //构建二维差分数组b[][]        
       }   
  }


完整代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e3 + 10;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
}
int main()
{
    int n, m, q;
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            cin >> a[i][j];
            insert(i, j, i, j, a[i][j]); 
        }
    }
    while (q--)
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
          	printf("%d ", b[i][j]);
          	//二维前缀和等价于
          	//a[i][j] = b[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
          	//cout << a[i][j] << " ";
        }
       printf("\n");
    }
    return 0;
}

(2022年蓝桥杯第三次模拟赛):清理水草

问题描述

小蓝有一个 n * m 大小的矩形水域,小蓝将这个水域划分为 n 行 m 列,行数从 1 到 n 标号,列数从 1 到 m 标号。每行和每列的宽度都是单位 1 。
现在,这个水域长满了水草,小蓝要清理水草。
每次,小蓝可以清理一块矩形的区域,从第 r1 行(含)到第 r2 行(含)的第 c1 列(含)到 c2 列(含)。
经过一段时间清理后,请问还有多少地方没有被清理过。

输入格式

输入第一行包含两个整数 n, m,用一个空格分隔。
第二行包含一个整数 t ,表示清理的次数。
接下来 t 行,每行四个整数 r1, c1, r2, c2,相邻整数之间用一个空格分隔,表示一次清理。请注意输入的顺序。

输出格式

输出一行包含一个整数,表示没有被清理过的面积。

样例输入1

2 3
2
1 1 1 3
1 2 2 2

样例输出1

2

样例输入2

30 20
2
5 5 10 15
6 7 15 9

样例输出2

519

评测用例规模与约定

对于所有评测用例,1 <= r1 <= r2 <= n <= 100, 1 <= c1 <= c2 <= m <= 100, 0 <= t <= 100。

思路:二维差分

上一题二维差分的应用,只不过矩阵的值均为1/0。

将本题抽象成:清理过等于把指定子矩阵元素+1,最后遍历矩阵为0的面积。

初始a为0矩阵,差分矩阵为其本身,因此不用构造。

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 110 ;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2)
{
    b[x1][y1] += 1;
    b[x2 + 1][y1] -= 1;
    b[x1][y2 + 1] -= 1;
    b[x2 + 1][y2 + 1] += 1;
}
int main()
{
    int n, m, t;
    cin >> n >> m >> t;
  
    int r1 , c1 , r2 , c2;
    while (t--)
    {
        cin >> r1 >> c1 >> r2 >> c2;
        insert(r1 , c1 , r2 , c2);
    }

    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
            //为0表示没清理过 计数器+1
          	if(!b[i][j]) cnt ++;
        }
    }
    cout << cnt << endl;
    return 0;
}

每日一题:540.有序数组中的单一元素

思路:二分,容易想到但不知道怎么写。

首先,数组的长度n一定是奇数,而且出现两次的数一定相邻,因此只出现一次的数必然位于偶数下标上。因此检查偶数下标2k即可。

  • if(nums[2k] == nums[2k + 1]) 只出现一次的数下标>2k,更新左边界l
  • if(nums[2k] != nums[2k + 1])只出现一次的数下标<2k,更新右边界。
  • 随着k增大,不等式nums[2k] != nums[2k + 1]越可能满足,因为该数越可能<2k

本题对k二分,k的初始范围为0<= k <=n/2 - 1 ,

❗ 注意
2k=n−1 的位置是不需要检查的,如果我们在<n−1的位置都没有找到只出现一次的数,那么只出现一次的数必然位于 n−1

初始化开区间 l = -1 , r = n / 2

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int n = nums.size();
        int l = -1 , r = n / 2;
        while(l + 1 < r){
          int mid = l + ((r - l) >> 1 );
          if(nums[mid * 2] != nums[mid * 2 + 1]) r = mid;
          else l = mid;
          //if else 也可用三目运算符
  //(nums[mid * 2] != nums[mid * 2 + 1] ? right : left) = mid;
        }
        return nums[r * 2];//最后该数下标即为2k
    }
};
posted @ 2024-11-10 22:00  七龙猪  阅读(1)  评论(0)    收藏  举报
-->