1. 前缀和与差分及其应用
有两个数组A和B。
前缀和数组A:
(a_1 , a_2, a_3..., a_n)
差分数组B:
(b_1, b_2, b_3, ..., b_n)
数组A和B的关系如下:
(1)前缀和数组A:
(a_1 , a_2, a_3..., a_n)又等于
(b_1, b_1+b_2, b_1+b_2+b_3, ..., b_1+b_2+...+bn)
(2)差分数组B:
(b_1, b_2, b_3, ..., b_n) 等于
(a_1, a_2 - a_1, a_3 - a_2, ..., a_n - a_{n-1})
前缀和和差分互为逆运算。因此,数组A是数组B的前缀和数组,数组B是数组A的差分数组。
差分数组的作用:
我们需要在A数组的某个区间【l,r】上(a[l], a[l+1], ..., a[r])频繁加上或者减去某个数c,一次操作需要对A数组操作r-l+1次,如果频繁操作,时间复杂度较高。但如果在数组A的差分数组上操作只需要(b_l+c), (b_{r+1}-c),再通过数组B重构出A数组,就可以达到在原A数组的区间【l,r】上加上c。简言之,求 a数组【l, r】区间所有数加一个数c, 则只需要 (b_l + c), 和 (b_{r+1} - c),在通过B数组重构A数组,即可以在a数组【l, r】区间所有数加一个数c。
分析:
(b_l+c)会影响到(a_l, a_{l+1}, a_n),他们是b数组的前缀和,即(a_l, a_{l+1}, a_n)变成了(a_l+c, a_{l+1}+c, a_n+c)。
(a_{r+1}, a_{r+2}, a_n)同理, (b_{r+1}+c)会影响到(a_{r+1}, a_{r+2}, a_n),他们是数组B的前缀和,即变成(a_{r+1}-c, a_{r+2}-c, a_n-c)了。
因此, (b_l + c), 和 (b_{r+1} - c)会使得即(a_l, a_{l+1}, a_r)变成了(a_l+c, a_{l+1}+c, a_r+c)。
如果1 <= l, r, <= n,我们由k(1 <= k <= n)次这样的操作,如果在数组A上直接操作,时间复杂度为(O(n^2)), 如果通过构造A的差分数组,每次操作为O(1),K次操作为O(n),最后再通过数组B重新构造出新数组A。
2. 例题: 路由器, 美团笔试题
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), k = sc.nextInt();
int[] a = new int[n];
for(int i=0; i < n; i++)
a[i] = sc.nextInt();
int[] b = new int[n]; //前缀和数组
int[] c = new int[n]; // 差分数组
for(int i=0; i < n; i++) {
int l = Math.max(i-a[i], 0);
int r = Math.min(i+a[i], n-1);
c[l] += 1;
if(r+1 < n) c[r+1] -= 1;
}
int res = 0;
// 通过C数组重构B数组
for(int i=0; i < n; i++) {
if(i > 0)
b[i] = c[i] + b[i-1];
else
b[0] = c[0];
if(b[i] >= k) res++;
}
System.out.println(res);
}
}