1. 前缀和
1.1 算法原理
所谓前缀和,就是记录下前方所有数据之和,当所需中间数据时,可以通过o(1)
的时间复杂度将数据求出。
- 一维数组前缀和
- 求出
1~i
的所有项之和。由于当运算到第
i
位时,前i-1
位已经运算完成,故a[i] = a[i] + a[i-1]
。
- 当需要
[l,r]
之和时,可以通过a[r]-a[l-1]
求出
- 二维数组前缀和
- 求出左上端点、右下端点分别为
(1,1)
,(i,j)
的长方形内的数据之和当运算到
(i,j)
时,前i-1
排、第i
排前j-1
位均已运算完成,故a[i][j] = a[i][j]+a[i-1][j]+a[i-1]-a[i-1][j-1]
。
- 当需要左上端点、右下端点分别为
(x1,y1)
,(x2,y2)
的长方形内的数据之和时,a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]
1.2 例题
参考代码:
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
while(m--){
cin>>l>>r;
cout<<a[r]-a[l-1]<<endl;
}
}
参考代码:
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
cin>>a[i][j],a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
while(q--){
cin>>x1>>y1>>x2>>y2;
cout<<a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]<<endl;
}
}
2. 差分
2.1 算法原理
所谓差分,就是记录每位数据与前一位数据之间的差,当需要对区间进行操作时,可以通过o(1)
的时间复杂度完成操作。
同时我们可以发现差分数组的前缀和就是原数组。
- 一维数组差分
求出与前一项之差。
当需要对
[l,r]
范围内的数进行加减操作时时,可以通过b[l]+c
、b[r+1]-c
实现
- 二维数组差分
二维差分数组的构建可以由其前缀和的结果来逆向考虑。
当对
(x1,y1)
,(x2,y2)
的长方形内的数据都加上c
时,即a[x1,y1]~a[x2,y2]
均加c
。故当b[x1,y1]+=c
,b[x1,y2+1]-=c
,b[x2+1,y1]-=c
,b[x2+1,y2+1]+=c
,即可实现仅区间内的数据加c
。
当构建差分数组时,可以发现其为对长宽为1的长方形加
a[i,j]
,由此实现二维差分数组。
2.2 例题
参考代码:
void solve(){
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) b[i] = a[i]-a[i-1];
while(m--){
cin>>l>>r>>c;
b[l]+=c;b[r+1]-=c;
}for(int i=1,t=0;i<=n;i++) t+=b[i],cout<<t<<' ';
}
参考代码:
void add(){
b[x1][y1] += c;b[x1][y2+1] -=c;
b[x2+1][y1] -= c;b[x2+1][y2+1] +=c;
}
void solve(){
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
cin>>a[i][j],x1=x2=i,y1=y2=j,c=a[i][j],add();
while(q--){
cin>>x1>>y1>>x2>>y2>>c;
add();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j] = b[i][j] + a[i-1][j]+a[i][j-1]-a[i-1][j-1];
cout<<a[i][j]<<' ';
}cout<<endl;
}
}
3. 双指针
3.1 算法原理
双指针问题主要是依据序列性质,使用指针j
对有效查询范围进行记录,从而减少运算的时间复杂度。
双指针问题主要分为下面两类:
- 在一个序列中,两个指针维护一段区间。
- 在两个序列中,维护某种次序
基本模板(来自AcWing)
void solve(){
for(int i=0,j=0;i<n;i++){
while(j<i && check(i,j)) j++;
//具体问题逻辑
}
}
3.2 例题
解决逻辑:设置cnt
数组记录[j,i]
区间内的数据出现次数,当i
指向数a[i]
已经出现时,让j
向前移动,直到[j,i-1]
中a[i]
出现次数为0。过程中记录长度。
参考代码:
void solve(){
for(int i=1,j=1;i<=n;i++){
if(cnt[a[i]]){
while(cnt[a[i]]) cnt[a[j++]]--;
}
cnt[a[i]]++;
ans = max(i-j+1,ans);
}cout<<ans<<endl;
}
解决逻辑:由序列升序,可以直到当a[i]+a[j]>x
时,a[i+1]+a[j]
必然大于x
。所以可以使用j
来记录需要判断的边界,从而减少搜索时间。
参考代码:
void solve(){
for(int i=0,j=m-1;i<n,j>0;i++){
while(a[i]+b[j]>x) j--;
if(a[i]+b[j]==x){
cout<<i<<' '<<j<<endl;
return 0;
}
}
}
解决逻辑:由子串是在母串中按次序出现,我们可以按位寻找子串每位在母串中对应的位置。
参考代码:
void solve(){
for(int i=0,j=0;i<n && j<m;i++,j++){
while(a[i] != b[j] && j<m) j++;
if(i==n-1 && j < m) {
cout<<"Yes"<<endl;return 0;}
}cout<<"No"<<endl;
}