差分算法:

差分算法:

1.差分的原理:

对于原始数组a[],差分数组diff[]的定义是:

diff[i] = a[i] - a[i - 1]

这是因为:

diff[1] = a[1]

diff[2] = a[2] - a[1]

diff[3] = a[3] - a[2]

diff[n] = a[n] - a[n - 1]

我们可以发现,对差分数组进行前缀和操作,可以让它变成原数组,因为:

diff[1] + diff[2] + diff[3] + … + diff[i]

= a[1] + (a[2] - a[1]) + (a[3] - a[2]) + … + (a[i] - a[i - 1])

= a[i]

2.差分的特点:

利用差分数组可以实现快速的区间修改,下面是区间[l, r]都加上x的方法:

diff[l] += x;
diff[r + 1] -= x;

这段代码的含义是:

现将区间[l, n]都加上x,但是呢,[r + 1, n]我们并不需要加上x,但是之前我们删除过一次了,所以我们需要减掉它。

在修改完成之后,我们需要做前缀和将它恢复为原数组。

恢复代码如下:

  for(int i = 1; i <= n; i++){		// 开始求差分数组
    diff[i] = a[i] - a[i - 1];
  }

  while(q--){
    int l, r, c;
    cin >> l >> r >> c;
    // 开始进行修改操作
    diff[l] += c;
    diff[r + 1] -= c;
  }
  // 修改完之后,将结果运用到原数组里面
  for(int i = 1; i <= n; i++){
    a[i] = a[i - 1] + diff[i];
  }

这样子之后,区间l ~ r都会加上c。

差分例题:

例题1:蓝桥OJ3693 肖恩的投球游戏:

链接如下:

https://www.lanqiao.cn/problems/3693/learning/?page=1&first_category_id=1&name=%E8%82%96%E6%81%A9%E7%9A%84%E6%8A%95%E7%90%83%E6%B8%B8%E6%88%8F

解题思路:

我们可以发现,这道题就是一个典型的差分和前缀和相关的题目,我们需要对原数组进行差分求出差分数组,然后循环进行q次操作

让它每次都在区间[l, r]上都能加上c个数字,进行完q次操作之后,就将结果运用到原数组中去,最后输出结果:

代码如下:

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], diff[N];    // 原数组和差分数组

int main()
{
  // 请在此输入您的代码
  int n, q;
  cin >> n >> q;
  // 初始化原数组和差分数组
  a[0] = 0;
  diff[0] = 0;
  for(int i = 1; i <= n; i++){
    cin >> a[i];
  }
  for(int i = 1; i <= n; i++){
    diff[i] = a[i] - a[i - 1];
  }

  while(q--){
    int l, r, c;
    cin >> l >> r >> c;
    // 开始进行修改操作
    diff[l] += c;
    diff[r + 1] -= c;
  }
  // 修改完之后,将结果运用到原数组里面
  for(int i = 1; i <= n; i++){
    a[i] = a[i - 1] + diff[i];
  }
  // 输出结果
  for(int i = 1; i <= n; i++)
    cout << a[i] << " \n"[i == n];
  return 0;
}

例题2:肖恩的投球游戏加强版(蓝桥OJ3694)

链接如下:

https://www.lanqiao.cn/problems/3694/learning/?page=1&first_category_id=1&name=%E8%82%96%E6%81%A9%E7%9A%84%E6%8A%95%E7%90%83%E6%B8%B8%E6%88%8F

解题思路

这个跟上一道题目不太一样,我们发现这个是需要二维的进行差分操作,再利用前缀和还原到原数组中。但是总体思路是不变的。

二维的差分是这样的:

D[x1][y1] += c;     D[x1][y2 + 1] -= c;
D[x2 + 1][y1] -= c; D[x2 + 1][y2 + 1] += c;

所以二维的原数组还原是这样的:

for (int i = 1; i <= n; i++) {
     for (int j = 1; j <= m; j++) {
         D[i][j] += D[i - 1][j] + D[i][j - 1] - D[i - 1][j - 1];
         a[i][j] += D[i][j];
     }

代码如下:

#include <iostream>
using namespace std;
const int N = 1e3 + 9;
int a[N][N], diff[N][N];
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] , diff[i][j] = a[i][j] - a[i][j-1];
   while(q--)
   {
       int x1,x2,y1,y2,c; cin >> x1 >> y1 >> x2 >> y2 >> c;
        for(int i = x1 ;i <= x2 ;i++)
         diff[i][y1] += c , diff[i][y2+1] -= c;

   }

   for(int i = 1 ;i <= n ;i++) 
    for(int j = 1 ;j <= m ;j++)
    a[i][j] = a[i][j-1] + diff[i][j];
      
    for(int i = 1 ;i <= n ;i++) 
    {
        for(int j = 1 ;j <= m ;j++)
           cout << a[i][j] << ' ';
        cout << '\n';
    }



     
  return 0;
}

我们也可以使用这个代码:

#include <bits/stdc++.h>
using namespace std;

#define MAX_N 1000
int a[MAX_N + 5][MAX_N + 5], D[MAX_N + 5][MAX_N + 5] = { 0 };

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];
        }
    }
    while (q--) {
        int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c;
        D[x1][y1] += c;     D[x1][y2 + 1] -= c;
        D[x2 + 1][y1] -= c; D[x2 + 1][y2 + 1] += c;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            D[i][j] += D[i - 1][j] + D[i][j - 1] - D[i - 1][j - 1];
            a[i][j] += D[i][j];
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

总结:

  • 差分可以用前缀和来求出原数组
  • 差分可以用来进行区间的数量的增加和减少,前提是操作前数组并没有进行改变,并且要在所有的修改操作完成之后,才能进行查询操作

写在最后:

     a[i][j] += D[i][j];
        cout << a[i][j] << " ";
    }
    cout << endl;
}
return 0;

}


总结:

- 差分可以用前缀和来求出原数组
- 差分可以用来进行区间的数量的增加和减少,前提是操作前数组并没有进行改变,并且要在所有的修改操作完成之后,才能进行查询操作



写在最后:

我们可以用这个网站学习C++知识:https://github.com/0voice
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值