差分及前缀和
-
一维前缀和(略
-
二维前缀和
预处理:
s u m x , y = ∑ i = 1 x ∑ j = 1 y a i , j sum_{x,y} = \sum_{i = 1}^{x}\sum_{j = 1}^{y}a_{i,j} sumx,y=∑i=1x∑j=1yai,j
s u m [ x ] [ y ] = s u m [ x ] [ y − 1 ] + s u m [ x − 1 ] [ y ] − s u m [ x − 1 ] [ y − 1 ] + a [ x ] [ y ] sum[x][y] = sum[x][y - 1] + sum[x - 1][y] - sum[x - 1][y - 1] + a[x][y] sum[x][y]=sum[x][y−1]+sum[x−1][y]−sum[x−1][y−1]+a[x][y]
查询:
求 ( x 1 , y 1 ) − ( x 2 , y 2 ) (x_1,y_1) - (x_2,y_2) (x1,y1)−(x2,y2) 子矩阵的和
s u m [ x 2 ] [ y 2 ] − s u m [ x 1 − 1 ] [ y 2 ] − s u m [ x 2 ] [ y 1 − 1 ] + s u m [ x 1 − 1 ] [ y 1 − 1 ] sum[x_2][y_2] - sum[x_1 - 1][y_2] - sum[x_2][y_1 - 1] + sum[x_1 - 1][y_1 - 1] sum[x2][y2]−sum[x1−1][y2]−sum[x2][y1−1]+sum[x1−1][y1−1]
题目:
P1387
const int N = 105;
int a[N][N];
int sum[N][N];
int main ()
{
//freopen("input.in", "r", stdin);
//freopen("test.out", "w", stdout);
int n, m;
read(n);
read(m);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
read(a[i][j]);
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
}
int ans = 0;
for (int d = 1; d <= min(n, m); d++) {
for (int i = 1; i + d - 1 <= n; i++)
{
for (int j = 1; j + d - 1 <= m; j++)
{
int x = i + d - 1;
int y = j + d - 1;
int _ = sum[x][y] - sum[x][j - 1] - sum[i - 1][y] + sum[i - 1][j - 1];
if(_ == d * d)
ans = max(ans,d);
}
}
}
cout << ans << endl;
return 0 ;
}
- 树上前缀和
设 s u m i sum_i sumi 表示节点 i i i 到根节点的权值总和。
若是点权, x , y x,y x,y 路径上的和为 s u m x + s u m y − s u m l c a − s u m f a l c a sum_x + sum_y - sum_{lca} - sum_{fa_{lca}} sumx+sumy−sumlca−sumfalca
若是边权, x , y x,y x,y 路径上的和为 s u m x + s u m y − 2 × s u m l c a sum_x + sum_y - 2 \times sum_{lca} sumx+sumy−2×sumlca
l c a lca lca :最近公共祖先
LOJ2491
-
差分
令 b i = a i − a i − 1 b_i = a_i - a_{i -1} bi=ai−ai−1 即相邻两数的差
易知: b i b_i bi 的前缀和是 a i a_i ai
一维差分:区间操作 [ l , r ] [l,r] [l,r] 区间加 k k k ,做法是 b l + k , b r + 1 − k b_l+k,b_{r+1} - k bl+k,br+1−k 最后再前缀和做一下就可以求得 a i + = k   ( l ≤ i ≤ r ) a_i += k \,(l\leq i\leq r) ai+=k(l≤i≤r) 的结果。
或者 b i = 0 b_i = 0 bi=0 , b l + k , b r + 1 − k b_l + k,b_{r+1} - k bl+k,br+1−k 之后取前缀和即可。
二维差分:方法是和一维类似的,我们也是需要另开一个数组记录修改操作,最后求前缀和时统计修改操作,只是二维每一次操作需要记录4个位置,一维只需要记录2个位置。最后对 b [ i ] [ j ] b[i][j] b[i][j] 求二维前缀和,再 a [ i ] [ j ] a[i][j] a[i][j] 中相加即可。
for(int i=0;i<m;i++){//m是修改操作次数
int x1,y1,x2,y2,p;
cin>>x1>>y1>>x2>>y2>>p;
b[x1][y1]+=p;b[x2+1][y2+1]+=p;
b[x2+1][y1]-=p;b[x1][y2+1]-=p
}
-
树上差分
- 树的性质:
1 、 1、 1、 树上任意两个点的路径唯一
2 、 2、 2、任何子节点的父亲节点唯一(根节点没有父亲)
- 点的差分:
s → t s \rightarrow t s→t 求这条路径上点被经过的次数
c n t i cnt_i cnti 表示节点 i i i 被经过的次数
做法: c n t s + + cnt_s++ cnts++ , c n t t + + cnt_t++ cntt++ , c n t l c a − − cnt_{lca}-- cntlca−−, c n t f a t h e r ( l c a ) − − cnt_{father(lca)}-- cntfather(lca)−−
考虑搜索到 s s s ,再从 s s s 回溯到 l c a lca lca 时, c n t l c a = 0 cnt_{lca} = 0 cntlca=0
搜索到 t t t ,再从 t t t 回溯到 l c a lca lca ,此时 c n t l c a = 1 cnt_{lca} = 1 cntlca=1
还有一个问题:如果从 l c a lca lca 点向上回溯时会使得父亲节点子树和为 1 1 1 ,所以需要在 c n t f a t h e r ( l c a ) − − cnt_{father(lca)}-- cntfather(lca)−−.
- 边的差分:
边的差分是把:边权赋到点上(常用套路,点通常时边所连的儿子节点。
c n t s + + , c n t t + + , c n t l c a − = 2 cnt_s++,cnt_t++,cnt_{lca}-=2 cnts++,cntt++,cntlca−=2
边权到儿子节点,在 d f s dfs dfs 中加上即可。
-
参考博客: