news 2026/4/24 13:37:01

《算法竞赛从入门到国奖》算法基础:入门篇-差分

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《算法竞赛从入门到国奖》算法基础:入门篇-差分

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

前言

1. 一维差分

算法原理

实操代码

2. 海底高铁

算法原理

实操代码

3. 二位差分

算法原理

实操代码

4. 地毯

算法原理

实操代码


前言

前缀和和差分的本质是预处理,可在暴力枚举的过程中,快速得到答案,是空间换时间的做法。另外,前缀和和差分是一对互逆的运算

1. 一维差分

题目链接:

【模板】差分

算法原理

我们引入差分数组的概念:

我们有一个整型数组a,定义差分数组f[n] = a[n] - a[n-1]

因此差分数组就是数组中一个数与前一个数的差值

现在我把原数组下标为[L,R]的范围内统一增加一个数字c

那么差分数组如何改变?由于是一个数减去前一个数,那么[L + 1,R]这个范围的值就不会改变

因为统一增加c,相当于(f[i] + c) - (f[i-1] + c) = f[i] - f[i-1]等于原来的值

但是如果是f[L]和f[R+1]就要变了,因为a[L]+c,a[R]+c

那么f[L] = a[L] + c - a[L-1],f[R+1] = a[R+1] - a[R] - c;

因此f[L]相比原来增加了c,f[R+1]相比原来减少了c

这这就是差分数组的性质。我们发现,如果我们统一对一段区间加上或减去一个相同的数,在原数组中需要挨个遍历,但差分数组只需要更改两个值即可。因此差分适用于统一对一段区间加上或减去一个相同的数这个操作

另外,对差分数组求前缀和就可以得到原数组

实操代码

#include <iostream> #include <vector> using namespace std; int main() { int a,b;cin>>a>>b; vector<long long> v(a+1,0);//原数组 vector<long long> del(a+1,0);//差分数组 for(int i = 1;i<=a;i++) { long long num;cin>>num; v[i] = num; } for(int i = 1;i<=a;i++) del[i] = v[i] - v[i-1];//初始化差分数组 while(b--) { int i,j,k;cin>>i>>j>>k; del[i] += k; del[j+1] -= k; } long long num = 0; for(int i = 1;i<=a;i++) { num += del[i];//差分数组求前缀和得到原数组 cout<<num<<" "; } return 0; }

2. 海底高铁

题目链接:

P3406 海底高铁 - 洛谷

算法原理

先考虑如何让花费最小,要想直到最小的花费,就必须得知道一段地铁坐了多少次,记f[n],随后算出买票的花费和买卡的花费之间的最小值

买票花费:a[i] * f[i]

买卡花费:b[i] * f[i] + c[i]

接下来考虑一段地铁坐了多少次,由于从i城市 到 j城市需要坐[i,j-1]之间的所有地铁,因此这实际上就是对一段区间统一加上一个数的操作,我们使用差分数组f[i],表示一段地铁坐了i次

实操代码

#include <iostream> #include <vector> using namespace std; int main() { int n, m; cin >> n >> m; vector<int> v; vector<int> a(n); vector<int> b(n); vector<int> c(n); vector<int> del(n + 1);//差分数组 for (int i = 0; i < m; i++) { int num; cin >> num; v.push_back(num); } for (int i = 1; i < n; i++) { int A, B, C; cin >> A >> B >> C; a[i] = A; b[i] = B; c[i] = C; } for (int i = 0; i < v.size() - 1; i++) { int l = v[i]; int r = v[i + 1]; if (l > r) { int tmp = r; r = l; l = tmp; } del[l]++; del[r]--; } long long ret = 0; long long k = 0; for (int i = 1; i < n; i++) { k += del[i]; long long cost1 = a[i] * k; long long cost2 = c[i] + b[i] * k; ret += cost1 > cost2 ? cost2 : cost1;//加上一段地铁的最小花费 } cout << ret; return 0; }

3. 二位差分

题目链接:

【模板】二维差分

算法原理

同样的对一段区间统一加值,不过现在是二维矩阵,我们需要推导出二维差分数组

我们利用前缀和的思想思考,假设我们对原数组左上角(x1,y1),右下角(x2,y2)的矩阵统一加上k

由于求差分数组的前缀和就可以得到原数组,那么在差分数组中,就相当于是(x1,y1)这个位置加k,这样(x1,y1)到(x2,y2)求前缀和就都能加上一个k

但是不仅是紫色这一部分,黄色,绿色和粉色求前缀和都能加上一个k,而原数组并没有对这些部分加k

因此我们需要对(x2+1,y1) (x1,y2+1)减去一个k来抵消

但是这样粉色部分又多减一个k,因此再在(x2 + 1,y2 + 1)加k

如何初始化二维差分数组?

我们这样思考。假设原数组全是0,读取一个数据k,就是某个方格加上k,也可以看做是(x1,y1)到(x2,y2)这个矩阵加k,不过这个矩阵很小只有一个方格,那么x2 = x1 ,y2 = y1

实操代码

#include <iostream> #include <vector> using namespace std; const int N = 1010; void insert(long long x1, long long y1, long long x2, long long y2, long long value, vector<vector<long long>>& v) { v[x1][y1] += value; v[x1][y2 + 1] -= value; v[x2 + 1][y1] -= value; v[x2 + 1][y2 + 1] += value; } int main() { int n, m, k; cin >> n >> m >> k; static vector<vector<long long>> v(N, vector<long long>(N, 0));//二维差分数组 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { long long value; cin >> value; insert(i, j, i, j, value, v);//初始化二维差分数组,x1 = x2,y1 = y2 } } for(int i = 0;i<k;i++) { long long x1, y1, x2, y2, value; cin >> x1 >> y1 >> x2 >> y2 >> value; insert(x1, y1, x2, y2, value, v);//某一个矩阵统一加上value } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { v[i][j] += v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1];//差分数组求前缀和得到原数组 cout << v[i][j] << " "; } cout << endl; } return 0; }

4. 地毯

题目链接:

P3397 地毯 - 洛谷

算法原理

铺一个地毯可以同时覆盖一个矩阵的点

我们直接套用二维差分数组即可

实操代码

#include <iostream> #include <vector> using namespace std; const int N = 1010; void insert(long long x1, long long y1, long long x2, long long y2, long long value, vector<vector<long long>>& v) { v[x1][y1] += value; v[x1][y2 + 1] -= value; v[x2 + 1][y1] -= value; v[x2 + 1][y2 + 1] += value; } int main() { int n, m; cin >> n >> m; static vector<vector<long long>> v(N, vector<long long>(N, 0)); for (int i = 0; i < m; i++) { long long x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2; insert(x1, y1, x2, y2, 1, v); } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { v[i][j] += v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1]; cout << v[i][j] << " "; } cout << endl; } return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 8:21:42

价格战背后的增长焦虑:影石大疆跨界“互搏”能走多远?

在智能影像设备市场&#xff0c;影石与大疆曾是各自细分赛道的绝对王者。影石长期垄断全景相机市场&#xff0c;全球市占率一度超过80%。大疆则统治着消费级无人机市场&#xff0c;70%以上的全球份额让其几乎没有对手。然而&#xff0c;一家独大的局面并非长久之计&#xff0c;…

作者头像 李华
网站建设 2026/4/19 18:25:46

18、网络安全防护:psad与fwsnort的应用与优势

网络安全防护:psad与fwsnort的应用与优势 1. 网络攻击与psad的应对 1.1 TCP连接与FIN扫描响应 在网络环境中,通过80端口与目标建立TCP连接本身并不一定意味着存在可疑活动。从传输层及以下来看,这种连接可能看似正常,iptables也不会记录任何信息。然而,盲FIN数据包则不…

作者头像 李华
网站建设 2026/4/20 23:18:23

17、Kubernetes存储管理全解析

Kubernetes存储管理全解析 1. 持久卷声明与挂载 在Kubernetes中,持久卷声明(PersistentVolumeClaim,PVC)是使用持久化存储的关键。在 volumes 下的 persistentVolumeClaim 部分,声明名称(如 storage-claim )能在当前命名空间内唯一标识特定的声明,并将其作为名…

作者头像 李华
网站建设 2026/4/16 21:02:41

20、在Kubernetes中运行有状态应用及自动扩缩容

在Kubernetes中运行有状态应用及自动扩缩容 1. 使用复制控制器部署Cassandra Cassandra是一个复杂的分布式数据库,有自动分发、平衡和复制数据的机制,这些机制并非针对网络持久存储进行优化,它设计为直接使用节点上存储的数据。当节点出现故障时,可通过其他节点上的冗余数…

作者头像 李华
网站建设 2026/4/17 8:21:30

26、网络安全:端口敲门与单包授权技术解析

网络安全:端口敲门与单包授权技术解析 1. 利用 Snort 签名增强防火墙功能 借助 Snort 社区提供的有效攻击检测签名,fwsnort 和 psad 项目能将 iptables 防火墙转变为可检测并响应应用层攻击的系统。本质上,这使 iptables 成为一个基础的入侵预防系统,具备阻止大量攻击与本…

作者头像 李华