浅谈差分和差分数组
洛谷链接 acwing链接
定义:
差分,又名差分函数或差分运算,差分的结果反映了离散量之间的一种变化,是研究离散数学的一种工具,常用函数差近似导数。百度百科
差分数组反应的是后面一个数和前面一个数的大小关系。我们定义
c
h
a
i
cha_i
chai 为
a
i
a_i
ai 的差分数组,表示 cha[i]=a[i]-a[i-1]
,主要用于
c
h
a
i
cha_i
chai 是定值,快速求出
a
j
a_j
aj 的时候。
差分则主要用于动态的区间修改和单点询问,见下。
差分性质:
- 如果将 l l l 到 r r r 的范围内的数加上 k k k,反应在数组上就是 c h a l + k cha_l +k chal+k 和 c h a r + 1 − k cha_{r+1}-k char+1−k,因为区间修改时范围内的数的相对大小关系,是不会发生变化的。这时 a i a_i ai 的值等于 ( ∑ i = 1 i c h a i ) + a i (\sum_{i=1}^i cha_i)+a_i (∑i=1ichai)+ai。
由此我们可以发现差分可以用于区间修改,单点询问的问题。
差分数组性质
- c h a 1 cha_1 cha1 的值可以是任意实数。
- a i a_i ai 的值等于 ( ∑ i = 1 i c h a i ) + a 1 (\sum_{i=1}^i cha_i)+a_1 (∑i=1ichai)+a1。(适用于 c h a i cha_i chai 为定值)
差分例题
典型例题一:
P2367 语文成绩
#include<bits/stdc++.h>
using namespace std;
int a[5000001];
int f[5000001];
int ans=100000;
int main(){
int n,p;
cin>>n>>p;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(p--){
int x,y,z;
cin>>x>>y>>z;
f[x]+=z;
f[y+1]-=z;
}
for(int i=1;i<=n;i++){
f[i]+=f[i-1];
a[i]+=f[i];
ans=min(ans,a[i]);
}
cout<<ans<<endl;
}
例题二:
P7404 家庭菜園 4
给定一个长为 N N N 的序列 A i A_i Ai, 你可以进行若干次操作:
- 选定一个区间 [ L , R ] [L,R] [L,R],让这个区间里的数加 1 1 1。
设经过这若干次操作后的序列为 B i B_i Bi ,那么你需要让 B i B_i Bi,满足下面这个要求:
- 存在一个整数 k ∈ [ 1 , N ] k \in [1,N] k∈[1,N],满足对于子序列 A 1 = { A 1 , A 2 , ⋯ , A k } A_1=\{A_1,A_2,\cdots,A_k\} A1={A1,A2,⋯,Ak} 为严格递增序列,对于子序列 A 2 = { A k , A k + 1 , ⋯ , A N } A_2=\{A_k,A_{k+1},\cdots,A_N\} A2={Ak,Ak+1,⋯,AN},为严格递减序列。
你想知道最少需要多少次操作才能满足上面这个要求。
代码与解析
例题三:
P6070 Decrease 和 P4552 IncDec Sequence。
其实二维的差分和一维类似,可以看成是
n
n
n 个一维差分。cha[i][j]=cha[i][j]-cha[i][j-1]
,想要把矩阵上的数调成零,就相当于将
c
h
a
i
,
j
cha_{i,j}
chai,j 变成
0
0
0。
#include<bits/stdc++.h>
using namespace std;
long long n,m,k,f[5001][5001],a[5001][5001],sum,ans,tot;
int main(){
cin>>n>>m>>k;
while(m--){
long long x,y,z;
cin>>x>>y>>z;
a[x][y]=z;
}
for(long long i=1;i<=n;i++){
for(long long j=1;j<=n;j++){
f[i][j]=a[i][j]-a[i][j-1];
}
}
for(long long i=1;i<=n-k+1;i++)
for(long long j=1,num=0;j<=n-k+1;j++){
num=f[i][j];
if(num!=0){
ans+=abs(num);
for(long long t=i;t<=i+k-1;t++)
f[t][j]-=num,f[t][j+k]+=num;
}
}
for(long long i=1;i<=n;i++)
for(long long j=1;j<=n;j++)
if(f[i][j]){
cout<<"-1"<<endl;
return 0;
}
cout<<ans<<endl;
}
#include<bits/stdc++.h>
using namespace std;
long long A,B,ANS;
long long ans=1e20;
const long long n=100001;
long long N,a[n],f[n];
int main() {
cin>>N;
for(long long i=1; i<=N; i++) {
cin>>a[i];
if(i!=1)f[i]=a[i]-a[i-1];
ANS+=f[i];
if(i!=1&&f[i]>0){
B+=f[i];
}
else if(i!=1&&f[i]<0){
A+=abs(f[i]);
}
}
ans=max(A,B);
cout<<ans<<endl;
cout<<abs(ANS)+1<<endl;
return 0;
}
例题四:
P3258 松鼠的新家
树上差分,对于每个节点存储 c h a i cha_i chai 和 a i a_i ai,从根节点开始递归。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3000001;
int a[MAXN],head[MAXN],tot,f[MAXN][30],final[MAXN],dep[MAXN],cha[MAXN];
struct shu {
int next,to;
} e[MAXN];
int prepare(int x,int father) {
dep[x]=dep[father]+1;
for(int i=0; i<=25; i++) {
f[x][i+1]=f[f[x][i]][i];
}
for(int i=head[x]; i; i=e[i].next) {
int q=e[i].to;
if(q!=father) {
f[q][0]=x;
prepare(q,x);
}
}
return 0;
}
int add(int from,int to) {
e[++tot].next=head[from];
e[tot].to=to;
head[from]=tot;
}
int LCA(int x,int y) {
if(dep[x]<dep[y])swap(x,y);
for(int i=20; i>=0; i--) {
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=20; i>=0; i--) {
if(f[x][i]!=f[y][i]) {
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int chafen(int x,int &di,int father) {
int tot=0;
int pan=1;
for(int i=head[x]; i; i=e[i].next) {
int to=e[i].to;
if(to!=father) {
chafen(to,tot,x);
pan=0;
}
}
if(pan) {
di+=cha[x];
final[x]+=cha[x];
} else {
final[x]+=cha[x]+tot;
di+=cha[x]+tot;
}
return 0;
}
int main() {
int n,N;
scanf("%d",&n);
N=n;
for(int i=1; i<=n; i++)scanf("%d",&a[i]);
while(--n) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
n=N;
prepare(1,0);
int now=a[1];
final[a[1]]++;
for(int i=2; i<=N; i++) {
int zhong=LCA(now,a[i]);
if(zhong==now) {
cha[now]--;
cha[a[i]]++;
} else if(zhong==a[i]) {
cha[f[a[i]][0]]--;
cha[f[now][0]]++;
} else {
cha[f[now][0]]++;
cha[zhong]-=2;
final[zhong]++;
cha[a[i]]++;
}
now=a[i];
}
final[a[N]]--;
int ter=0;
chafen(1,ter,0);
for(int i=1;i<=n;i++)cout<<final[i]<<endl;
}