手撕算法——差分

        前缀和与差分的核⼼思想是预处理,可以在暴⼒枚举的过程中,快速给出查询的结果,从⽽优化时间复杂度。

        是经典的⽤空间替换时间的做法。

        学完差分之后,⼤家会发现,前缀和与差分是⼀对互逆的运算。

一、⼀维差分

题⽬来源:⽜客⽹

题⽬链接:【模板】差分

难度系数:★

1. 题目描述

2. 算法原理

解法一:暴力解法-->直接模拟 (时间复杂度过高)

解法二 :利用差分数组解决问题。

        差分模板题,先创建差分数组,根据差分数组的「性 次区间改,最后「还」出来原始的数组。

  1. 创建差分数组,根据定义f [i] = a[i] − a[i 1]

        也可以根据差分数组的f [i] + = a[i], f [i + 1] − = a[i]

  1. 根据差分数组的质处 区间改:f [L] + = c, f [R + 1] − = c

  1. 原经 q 问之后的 a 数组:对差分数组⼀次前缀和,就可以还原出原数组

由差分数组的定义得 :            原数组 a 中的每⼀项

注意差分适用于只最后一次查询的题目,如果遇到边查询边输出的题目使用线段树。

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n, m;
LL f[N]; // 差分数组

int main()
{
    cin >> n >> m;
    // 利用差分数组的性质,创建差分数组
    for(int i = 1; i <= n; i++)
    {
        LL x; cin >> x;
        f[i] += x;
        f[i + 1] -= x;
    } 
    
    // 处理 m 次修改操作
    while(m--)
    {
        LL l, r, k; cin >> l >> r >> k;
        f[l] += k; f[r + 1] -= k;
    }
    
    // 还原出原始的数组
    for(int i = 1; i <= n; i++)
    {
        f[i] = f[i - 1] + f[i];
        cout << f[i] << " ";
    }
    
    return 0;
}

二、海底⾼铁

题⽬来源:洛⾕

题⽬链接:P3406 海底高铁 - 洛谷

难度系数:★★

1. 题目描述 

2. 算法原理

        先考虑何让花费最⼩,想要求最⼩费,需每⼀段⾼铁被「了多少次记作么最⼩费就是买票的买卡的两者之间的最⼩值:

  • 买票a[i] × f [i] f [i]
  • 买卡费,乘⻋ + ⼯本费b[i] × f [i] + c[i]
  • 么最⼩费就是min cost = min(a[i] × f [i], b[i] × f [i] + c[i])

下来考求出每⼀段⾼铁被「了多少次」。

根据访市的序列p1 , p2 , p3 , ..., pm可知

        对于任意⼀次访问pi pi+1 ,我们会坐[pi, pi+1 1]之间所有的⾼铁,⽐如pi = 3, pi+1 = 6,那么[3, 5]之间所有的⾼铁都会被⼀次,相当于每个数加上1 「注意 6位置不坐到」。那么我们就可利⽤差分数组」:

  • 创建⼀个全为 0 的差分数组 f 
  • 访问序列,对于每⼀次访f [pi] + +, f [pi+1 ] − −
  • 然后对差分数组⼀次前缀和,就得到每个⾼铁乘的次数

注意城访问的序列有可pi > pi+1 ,此时应该「换」⼀下

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n, m;
LL f[N]; // 差分数组

int main()
{
    cin >> n >> m;
    // x->y
    int x; cin >> x;
    for(int i = 2; i <= m; i++)
    {
        int y; cin >> y;
        // x -> y
        if(x > y) 
        {
            f[y]++;
            f[x]--;
        }
        else 
        {
            f[x]++;
            f[y]--;
        }
        x = y;
    }

    // 利用差分数组,还原出原数组
    for(int i = 1; i <= n; i++) f[i] += f[i - 1];

    // 直接求结果
    LL ret = 0;
    for(int i = 1; i < n; i++)
    {
        LL a, b, c; cin >> a >> b >> c;
        ret += min(a * f[i], c + b * f[i]);
    }

    cout << ret << endl;

    return 0;
}

三、⼆维差分

题⽬来源:⽜客⽹

题⽬链接:【模板】二维差分

难度系数:★

1. 题目描述 

2. 算法原理

⼆维差分板题,先根据差分矩阵的「性创建差分矩阵,根据差分矩阵的「性 q  区间改,最后利⽤前缀和」还原出来原始的矩阵。因此,重点就是差分矩阵的「性」。

类⽐⼀维差分数组质,导出⼆维差分矩阵

  • 差分数组中标记:表⽰后续元统⼀被修
  • 差分数组中求前缀和:能原出原始数组

假设将原始矩阵  中,以 (x1 , y1 ) 为左上⻆, (x2 , y2 ) 为右下⻆的⼦矩阵的每个元素都加上 k

由此可得差分矩阵的

f [x1 ][y1 ]+  =  k

f [x1 ][y2 + 1]− = k

f [x2 + 1][y1 ]− = k

f [x2 + 1][y2 + 1]+ = k

3. 参考代码

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1010;

int n, m, q;
LL f[N][N]; // 差分矩阵

// 差分矩阵的性质
void insert(int x1, int y1, int x2, int y2, LL k)
{
    f[x1][y1] += k; f[x1][y2 + 1] -= k; f[x2 + 1][y1] -= k; f[x2 + 1][y2 + 1] += k;
}

int main()
{
    cin >> n >> m >> q;
    // 预处理差分矩阵
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            LL x; cin >> x;
            // [i, j]为左上角,[i, j]为右下角的矩阵,统一加上 x
            insert(i, j, i, j, x);
        }
    }
    
    // 处理 q 次修改操作
    while(q--)
    {
        LL x1, y1, x2, y2, k; cin >> x1 >> y1 >> x2 >> y2 >> k;
        insert(x1, y1, x2, y2, k);
    }
    
    // 利用前缀和还原出修改之后的数组
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
            cout << f[i][j] << " ";
        }
        cout << endl;
    }
    
    return 0;
}

四、地毯

题⽬来源:洛⾕

题⽬链接:P3397 地毯 - 洛谷

难度系数:★

1. 题目描述

2. 算法原理

直接利⽤⼆维差分矩阵即可。

3. 参考代码

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int f[N][N]; // 差分矩阵

// 差分数组的性质
void insert(int x1, int y1, int x2, int y2, int k)
{
    f[x1][y1] += k; f[x1][y2 + 1] -= k; f[x2 + 1][y1] -= k; f[x2 + 1][y2 + 1] += k;
}

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

    // 构建差分数组
    while(m--)
    {
        int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
        insert(x1, y1, x2, y2, 1);
    }

    // 利用前缀和还原修改之后的数组
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
            cout << f[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值