一维差分简单定义就是数组的每个当前项都减去前一项(有点错位相减得新数组的意思),这样得到的数组称为差分数组。
假设数组
s
u
b
[
n
+
2
]
sub[n + 2]
sub[n+2] 为
a
[
n
]
a[n]
a[n] 的差分数组。
那么值得注意的是,差分数组的第一项为
s
u
b
[
1
]
=
a
[
1
]
−
0
sub[1] = a[1] - 0
sub[1]=a[1]−0,最后一项是
s
u
b
[
n
+
1
]
=
0
−
a
[
n
]
sub[n + 1] = 0 - a[n]
sub[n+1]=0−a[n],相当于错位时,空的位置默认为0.
差分可以解决数组内某一块元素分别进行相同加减的变换,比如将数组下标为
[
2
,
7
]
[2, 7]
[2,7] 区间的元素全部加上6,我们可以先计算出最初的
s
u
b
sub
sub差分数组,然后再在
s
u
b
[
2
]
sub[2]
sub[2] 加上6,
s
u
b
[
8
]
sub[8]
sub[8] 减去6,再运用差分数组的性质,就可以还原为改变后的数组。(不懂先留着往下看)
这样看来差分没有很大优势,但是当情况变为n个数字,m种元素变换方式时,差分数组的运用就能大大减小时间复杂度,若用暴力解决,时间复杂度必然为
O
(
n
m
)
O(nm)
O(nm) ,线段树或树状数组就是
O
(
m
l
o
g
n
)
O(mlogn)
O(mlogn) ,而差分可以将时间复杂度降低至
O
(
n
)
O(n)
O(n) 。只需要每次改变要求区间的首位元素和末位元素的下一位的差分数组的值,再在最后,运用
a
[
i
]
=
a
[
i
−
1
]
+
s
u
b
[
i
]
a[i] = a[i - 1] + sub[i]
a[i]=a[i−1]+sub[i] 求出所求数组即可。(感觉有点线段树呐味儿了)
不难想到,差分数组运用的性质就是当数组元素按块被加减进行变化时,块内的元素两两之间的差是不会改变的。 所以在运用相同的加减运算改变数组内某块的值时,其实在整个数组当中,改变的只是被赋值块的块头元素与前一个元素的差值,与被赋值块的块末元素与其后一个元素之间的差值。这就是为什么之前改变差分数组时,只改变块头元素 和 块末元素的下一个 的差值的原因。
(这道题就差分直接套用,十分方便
链接: https://codeforces.com/contest/1501/problem/B)
例题:一道在CF上补到的可以运用差分的题。
题目链接:https://codeforces.com/contest/1491/problem/C
按照题意,想要用最少的蹦跳轮数将所有蹦床的蹦跳距离磨损变为一,那么只需要保证每轮踩到蹦床的数量最多就好了,就是贪心的想法。再在纸上模拟一下,加入在第
a
a
a 轮的蹦跳过程中,我们踩到了蹦床
i
i
i,那么我们蹦跳的下一个床必然是
i
i
i + si,所以不论假设第一张蹦跳的床是什么,他的蹦跳路线都是固定的,要想经过的床最多,那么起始床一定是第一张床。
由于当第一张床的弹力值为1时,我们的下一个蹦跳位置必然为第二张床,所以,我们只需要用一个 f o r for for 循环,将所有床从1 ~ n都蹦为弹跳值为1即可。
因为每次的蹦跳都会导致床的弹力衰减1,所以每次踩上同一个床时,都会比上一次少蹦一个位置,一直到只能蹦一个位置为止。这就等同与想要将一个弹力为 si 的床变为弹力为1的床,我们需要在上面蹦
m
a
x
(
1
,
max(1,
max(1, si
−
1
)
- 1)
−1) 次,而
第一次我们会被弹到
m
i
n
(
n
,
min(n,
min(n, si
−
1
+
i
)
- 1 + i)
−1+i) 张床,
第二次会被弹到
m
i
n
(
n
,
min(n,
min(n, si
−
1
+
i
−
1
)
- 1 + i - 1)
−1+i−1) 张床
第三次会被弹到
m
i
n
(
n
,
min(n,
min(n, si
−
1
+
i
−
2
)
- 1 + i - 2)
−1+i−2) 张床
…
第
m
a
x
(
1
,
max(1,
max(1, si
−
1
)
−
1
- 1) - 1
−1)−1 次会被弹到
m
i
n
(
n
,
i
+
2
)
min(n, i +2)
min(n,i+2) 张床
所以就相当于在以第
i
i
i 张床为起点的床上,当我们将其弹跳值蹦为1时,我们会经过
m
i
n
(
n
,
i
+
2
)
min(n, i +2)
min(n,i+2) ~
m
i
n
(
n
,
min(n,
min(n, si
−
1
+
i
)
- 1 + i)
−1+i) 张床,这些床的弹跳值都会被减1,就是差分的思想了。每层for都将
[
i
+
2
,
m
i
n
(
n
,
s
i
+
i
)
]
[i + 2, min(n, s~i~ + i)]
[i+2,min(n,s i +i)] 的元素减一,就是改变
i
+
2
i + 2
i+2 和
m
i
n
(
n
,
min(n,
min(n, si
+
i
)
+ i)
+i) 的差分。
大佬的代码加自己的理解:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
int s[maxn], b[maxn];
int main (){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin >> t;
while(t --){
memset(b, 0, sizeof(b));
int n; cin >> n;
for(int i = 1; i <= n; i ++) cin >> s[i];
ll ans = 0;
int las = 0;
for(int i = 1;i <= n; i ++){
int now = b[i] + las; //now表示是i床之前被跳了几次,即b[i]
las = now; //之前被跳过的次数
if(s[i] - now < 1){
b[i+1] += now - s[i] + 1;//在进行第i步之前i弹力已经为1,所以i上面多跳的步数,转移到i+ 1.
b[i+2] -= now - s[i] + 1;//减去多算的
now = s[i] - 1;
}
ans += s[i] - now - 1;
if(min(s[i] + i, n) >= i + 2){//差分
b[i + 2] ++;
b[min(s[i] + i, n) + 1] --;
}
}
cout << ans << endl;
}
return 0;
}