差分习题:小明的彩灯,牛妹吃豆子(c++实现,含思路,数据范围,定义数组个数 详解)

目录

1.一维数组的差分:小明的彩灯

题意理解: 

注意点 :

注意本题的数据范围的界定。

增量x 可能为负

2. 二维数组的差分:牛妹吃豆子

先看第一个问题,在某块区域内放豆子。

数组个数的定义:

第二个问题, 计算某块区域内有多少豆子。

这道题的数据范围,还是两看。


1.一维数组的差分:小明的彩灯

题意理解: 

读完题我们应该清晰,这道题需要我们做的操作是,首先输入所给数组(看作前缀和的数组)同时直接还原出原数组(也就是差分数组),接着通过一维数组差分的思想,在差分数组中将满足所要求的区域内的值进行更改,最后执行输出。

注意点 :

注意本题的数据范围的界定

一看输入数据的个数,本题中N数组元素个数最大是5e5,即数组大小可确定了;二看数组中数据的大小来确定定义的数据类型,在这道题里需要定义两个数组,一个数组表示题中所给数组(看作前缀和的数组),另一个表示差分数组。

在这道题里必须使用long long类型定义两个数组。

首先是差分数组,由于差分数组中某几个值修改后的结果取决于执行的次数,即Q(在代码中我用m来表示)的大小,当Q取到了其最大可取值5e5,如果其本身又是接近10^9,那么此时其最大值可达到,二者相乘所得数据,大概是10^14,已经超过了int类型所表示的最大10^9,所以差分数组应定义为long long 类型。

其次在后面对差分数组中的数据更改之后,要根据其更改后的值来修改原前缀和数组中的值,所以应该也定义为long long类型。

在大多数情况下,int类型能够表示的整数范围大约是-2.1e9到2.1e9,即-2.1 * 10^9到2.1 * 10^9。

long long类型能够表示的整数范围大约是-9.2e18到9.2e18,即-9.2 * 10^18到9.2 * 10^18。

增量x 可能为负

这是一个直白的一维数组差分的题目,在考虑数据范围之外需要注意的就是 增量x 可能为负,题中要求如果为负,直接输出为0即可。这里我们可以从通过遍历最终所得的前缀和数组,使用if语句实现题中所求。

这里要注意不可在前缀和数组未求完的基础上直接将其值修改为0,如下错误写法

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=m;j++)
    {
        q[i] = q[i - 1] + p[i];
        if (q[i] < 0)
            q[i] = 0;
        cout << q[i] << " ";
    }
}

因为我们知道求前缀和需要q[i]=q[i-1]+p[i]的,还需要用的q[i-1]呢,不可直接更改其值导致后续值的错误。

C++代码如下

#include <iostream>
using namespace std;
const int N = 5e5 + 10;
long long p[N],q[N];
int n, m;
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> q[i];
        p[i] = q[i] - q[i - 1];
    }
    while (m--)
    {
        int l, r, x;
        cin >> l >> r >> x;
        p[l] += x;
        p[r + 1] -= x;
    }
    for (int i = 1; i <= n; i++)
    {
        q[i] = q[i - 1] + p[i];
    }
    for (int i = 1; i <= n; i++)
    {
        // cout << max(q[i],(long long)0) << " ";这里注意将0强制类型转化为long long
        if (q[i] < 0)
            q[i] = 0;
        cout << q[i] << " ";
    }
    return 0;
}

好啦这道题先到这里

1.数据范围想明白

2.最后一步,若前缀和为负输出值为0 操作的实现 

2. 二维数组的差分:牛妹吃豆子

看完题目我们需要先理清题目要求做什么。首先是在一块区域内放豆子,接着是计算某块区域内有多少豆子。

先看第一个问题,在某块区域内放豆子。

在这道题中并没有给出我们原始数组(即应当看作前缀和的那个数组),因为我们想要实现在某块区域内的豆子数量都增加1,这是直接用到了差分数组的思想,所以我们在这道题中不需要定义原数组,只需定义差分数组即可。

数组个数的定义:

在这里可能要好好区分一下什么时候是定义一个数组就可以,什么时候必须定义两个数组(是我需要hhh)。

当需要对初始化为0的数组某个区域内的值 进行相同操作时,只需开辟一个数组;当需要对数组中的单个点进行操作的时候,就需要开辟两个数组

解释如下。

差分数组只能表示相邻元素之间的差值,而无法表示单个元素的值。

当数组初始化为0时,想要实现对某个区域内的值做相同的改变,我们可以只使用差分数组来解决,不需要开辟原数组。我们可以建立一个差分数组q,其中q[i]表示q[i]与p[i-1]之间的差值。初始时,由于p中所有元素都为0,因此q中所有元素也都为0。所以这时直接q数组就可以 通过自身值根据需要的不断更新来 实现两个数组的功能,而不需要开辟两个数组了。

数组初始化为0:当我们只使用差分数组而不使用原数组时,我们无法知道原数组中每个元素的初始值。因此,我们只能假设原数组中所有元素的初始值都为0,并据此来初始化差分数组。

但是对于需要对单个点进行的操作,我们需要同时修改原数组q和差分数组p中的元素。

第二个问题, 计算某块区域内有多少豆子。

这个问题就是我们比较熟悉的通过求二维数组数组前缀和来计算某个区域内的值之和的操作,不再多说啦。

这道题的数据范围,还是两看。

一看输入数据的个数。这里是2000*2000的数组,即可确定常量N的值

二看数组中数据的大小来确定定义的数据类型。在k这里最多是把棋盘中的数从0加到了200000,这里还是int类型可以满足的。但在下面,得出棋盘格之后,我们又对其执行了前缀和的操作,2000*2000*200000=8*10^11超出int范围,所以需要定义数据类型为long long。

#include <iostream>
using namespace std;
const int N = 2001;
long long p[N][N];
int n, m, k, q;
int main()
{
    cin >> n >> m >> k >> q;
    int x1, y1, x2, y2;
    // 放豆子 对差分数组中某些数据的修改
    while (k--)
    {
        cin >> x1 >> y1 >> x2 >> y2;
        p[x1][y1] += 1;
        p[x1][y2 + 1] -= 1;
        p[x2 + 1][y1] -= 1;
        p[x2 + 1][y2 + 1] += 1;
    }
    // 实现 对前缀和数组中 某块区域值的修改
    // 接下来的操作是为了得到放完豆子后的棋盘【前缀和数组】
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            p[i][j] = p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1] + p[i][j];
        }
    }
    // 由于所求的就是现在这个棋盘中某块区域的值的和,所以接下来就完全是二维前缀和算法的实现了
    // 需要有一个思维的转变,这时棋盘表示的数组就应当看作 在前缀和算法中的 原数组了
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            p[i][j] = p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1] + p[i][j];
        }
    }
    // 所求区域的前缀和
    while (q--)
    {
        cin >> x1 >> y1 >> x2 >> y2;
        cout << p[x2][y2] - p[x1 - 1][y2] - p[x2][y1 - 1] + p[x1 - 1][y1 - 1] << endl;
    }
    return 0;
}

好啦,这次就先写到这里了。


有问题欢迎指出,非常感谢!

也欢迎交流建议哈

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值