总的来说
1.前缀和:对一个序列进行O(N)预处理(或者一个矩阵,即二维前缀和)后,O(1)地查询任意一段子序列的和.
2.差分:用于求解多次区间修改与区间询问的题型,例如多次给[ L , R ] 内所有数 + val,就可以用差分以及前缀和来优化。区间操作O(1),区间询问O(N)处理,O(1)查询.
3.树上差分:同样的,如果我们有若干次操作与若干次询问,每次操作对从u 到 v 路径上所有节点加一个 值,那么我们用树上差分可以将时间复杂度控制在O(1)上,询问同样是O(N)处理,O(1)查询.
注意:差分为离线算法,不支持同时修改同时询问操作,算法复杂度会退化
1.前缀和
1.1 问题描述:给出一串长度为 n 的数列a1,a2,a3…an,再给出m个询问,每次询问给出L,R两个数,要求给出区间[L,R]里的数的和。
1.2基本思路:朴素算法,将[L,R]中每个数依此累加,复杂度为O(MN);
前缀和的思路:开一个数组d[];
d[i] = a[i] + a[i-1] + a[i-2] + ... + a[1];
记录序列从开始至第i位置的总和,由此我们可以得出任意一段序列[1,x]的和,即[1,L-1],[1,R],ans = d[R] - d[L - 1],复杂度为O(1);预处理操作复杂度为O(N);
3.一维差分
3.1 问题描述:给出一串长度为 n 的数列a1,a2,a3…an , 给出k次操作,每次操作给出L、R、val,要求对 [L , R]内所有元素加上val,再给出m个询问,每次询问给出L,R两个数,要求给出区间[L,R]里的数的和。
3.2 朴素思路:很容易想到的做法就是每次给出L,R 和 val后,我们就挨个对[L,R] 内所有元素+val,再在 询问时将答案挨个加起来。这样时间复杂度为O(kmn)
3.3 差分思路:顺应着前缀和,假如我们将所有修改操作进行一次前缀和,在所有修改操作完成后,进行一次总的修改,即大大降低时间复杂度;
3.4 一维差分实现思路:开一个c[]数组,用来记录修改的前缀和,假设我们对区间[L,R],进行修改,我们只需令c[L] += val,c[R+1] -= val,将所有修改操作类此标记,在最后对c[]数组进行一次前缀和,将原先处理好的d[]前缀和数组,按位进行加法,即得最终修改值;
c[L] += val;c[R+1] -= val;
一维差分前缀和
c[L] = val;c[L+1] = val;......c[R] = val;c[R+1] = 0;//为什么?因为我们已经将c[R+1]标记为了 -val;
修改数组d[];
for(i,1...n)
d[i] += c[i];
2.二维前缀和
2.1 公式:
d[i][j] = Σ(a[i...0][j...0]);
2.2 基本问题:给定一个n*m大小的矩阵a,有q次询问,每次询问给定x1,y1,x2,y2四个数,求以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素和。注意仍然包含左上角和右下角的元素。
二维前缀和实现思路:d[i][j]记录以i,j为右下角下标,以1,1为左上角下标的矩阵内所有数据域之和;实现询问操作:
图解
红色矩阵a,黄色为目标矩阵;ans = (黄,灰,蓝,紫四个矩阵) - (灰,蓝两个矩阵) - (蓝,紫两个矩阵)+(蓝色矩阵);这里我们以蓝色矩阵为过渡,O(1)时间内求解完毕;
Talk is cheap.
预处理操作
//d[i][j]应预先存好第(i,j)位置的元素值;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
d[i][j] += d[i][j-1] + d[i-1][j] - d[i-1][j-1];//类似于询问操作
}
询问操作
ans = d[x2][y2] - d[x1-1][y2] - d[x2][y1-1] + d[x1-1][y1-1];//x1 - 1和y1 - 1是因为题目要包含左上角和右下角的元素;
4.二维差分
思路引进:既然一维前缀和能差分,二维同样可以;(假的证明)
实质上是只不过是把差分从一维引进到二维而已;
同样开一个c[][]数组来维护修改操作;
for(int i = 1;i <= m;i++){//m为修改次数;
int x1,y1,x2,y2,p;
cin>>x1>>y1>>x2>>y2>>p;
c[x1][y1] += p;c[x2+1][y2+1] += p;
c[x2+1][y1] -= p;c[x1][y2+1] -= p;//与一维差分相同,都需在后一位维护一个-p;
}
后续操作均与一维差分类似,在此不再赘述;
5.树上差分
5.1 问题描述:
· 给定一棵有N个点的树,所有节点的权值初始时都为0。
· 有K次操作,每次指定两个点u , v,将 u 到 v 路径上所有点的权值都+1。
· 请输出K次操作完毕后权值最大的那个点的权值。
5.2 朴素思路:不用多想,最暴力的做法就是我们找到 u 到 v 路径上的所有点并+1(可以利用 LCA )。最后,再遍历所有点查找权值最大的点并输出。这样时间复杂度为O(KN),这还不包括 LCA 查找路径的时间。非常暴力
5.3 求u到v的路径:那么我们知道,如果假设我们要考虑的是从 u 到 v 的路径,u 与 v 的 lca 是 a ,那么 很明显,假如路径中有一点 u′ 已经被访问了,且 u′ ≠ a ,那么 u’ 的父亲也一定会被访问。(为什么呢?因为这是一棵树)所以,我们可 以将路径拆分成两条链,u -> a 和 a -> v。
图解
(图糙,轻喷)