5.差分
优化暴力枚举
前缀和与差分的核心思想都是预处理, 可以在暴力枚举中快速给出查询结果, 从而优化时间复杂度
经典的用空间换时间的做法
前缀和 和 差分 是一对互逆运算
5.1 一维差分
!牛客网
思路:
利用差分数组解决问题
差分数组的作用: 快速解决"将某一个区间所有元素统一加上一个数" 的操作
O(1)
- 预处理差分数组
f[i]表示: 当前元素与以前一个元素的差值 - 利于差分数组解决m次修改操作
差分数组的性质: 原数组[L, R]区间全部加k这个操作 = 在差分数组中f[L] += k; f[R+1] -= k; - 差分数组 -> 原数组(还原)
a[i] = f[i] + a[i-1];
注意事项:
差分数组使用时, 所有操作必须全部进行完后, 才能还原出操作之后的数组(差分数组使用条件)
代码:
暴力解法 -> 枚举
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL a[N];
LL n, m;
LL l, r, k;
int main()
{
cin >> n >> m;
for(int i = 1; i<= n; i++) cin >> a[i];
while(m--){
cin >> l >> r >> k;
for(int i = l; i <= r; i++) a[i]+=k;
}
for(int i = 1; i<= n; i++) cout << a[i] << " ";
return 0;
}
部分用例会超时
差分数组解决:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL a[N];
LL n, m;
LL l, r, k;
LL f[N];
int main()
{
cin >> n >> m;
for(int i = 1; i<= n; i++) {
cin >> a[i];
//构造差分数组
f[i] = a[i] - a[i-1];
}
while(m--){
cin >> l >> r >> k;
//利用差分数组的性质进行操作
f[l] += k;
f[r+1] -= k;
}
for(int i = 1; i<= n; i++){
a[i] = f[i] + a[i-1];
cout << a[i] << " ";
}
return 0;
}
5.2 海底高铁
!洛谷
P3406 海底高铁
题目描述
该铁路经过 N N N 个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第 i i i 段铁路连接了城市 i i i 和城市 i + 1 ( 1 ≤ i < N ) i+1(1\leq i<N) i+1(1≤i<N)。如果搭乘的比较远,需要购买多张车票。第 i i i 段铁路购买纸质单程票需要 A i A_i Ai 博艾元。
虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了 IC 卡。对于第 i i i 段铁路,需要花 C i C_i Ci 博艾元的工本费购买一张 IC 卡,然后乘坐这段铁路一次就只要扣 B i ( B i < A i ) B_i(B_i<A_i) Bi(Bi<Ai) 元。IC 卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第 i i i 段铁路的 IC 卡,无法乘坐别的铁路的车。
Uim 现在需要出差,要去 M M M 个城市,从城市 P 1 P_1 P1 出发分别按照 P 1 , P 2 , P 3 , ⋯ , P M P_1,P_2,P_3,\cdots,P_M P1,P2,P3,⋯,PM 的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。
现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。
输入格式
第一行两个整数, N , M N,M N,M。
接下来一行, M M M 个数字,表示 P i P_i Pi。
接下来 N − 1 N-1 N−1 行,表示第 i i i 段铁路的 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci。
输出格式
一个整数,表示最少花费
输入输出样例 #1
输入 #1
9 10 3 1 4 1 5 9 2 6 5 3 200 100 50 300 299 100 500 200 500 345 234 123 100 50 100 600 100 1 450 400 80 2 1 10输出 #1
6394说明/提示
2 2 2 到 3 3 3 以及 8 8 8 到 9 9 9 买票,其余买卡。
对于 30 % 30\% 30% 数据 M = 2 M=2 M=2。
对于另外 30 % 30\% 30% 数据 N ≤ 1000 , M ≤ 1000 N\leq1000,M\leq1000 N≤1000,M≤1000。
对于 100 % 100\% 100% 的数据 M , N ≤ 10 5 , A i , B i , C i ≤ 10 5 M,N\leq 10^5,A_i,B_i,C_i\le10^5 M,N≤105,Ai,Bi,Ci≤105。
思路:
- 先知道每一段高铁被乘坐了多少次, 记作
num[i] - 最小花费 = 买票花费与(买卡: 乘车+工本费)的最小值
A / B C
mincost = min(a[i] * num[i], b[i]* num[i] + c[i]); - 每一次访问从一地到另一地都会经过中间所有铁路, 即 [ p i , p i + 1 − 1 ] [p_i,p_i+1 -1] [pi,pi+1−1]
- 创建一个全为0的差分数组f
- 遍历访问序列 , 每次访问 : f [ p i ] + + , f [ p i + 1 ] − − f[p_i]++, f[p_i+1]-- f[pi]++,f[pi+1]−−
- 然后对差分数组做一次前缀和, 就能得到每个高铁乘坐的次数
- 注意, 城市访问序列有可能是从大序到小序, 此时交换数据即可
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
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], b*f[i] + c);
}
cout << ret;
return 0;
}
