【学习笔记】进阶算法——倍增
什么是倍增?
倍增?倍增?倍增!
之前学了最近公共祖先 LCA,其是倍增的子问题。
倍增是什么,什么是倍增?倍增,顾名思义,就是一倍两倍往上增。其实上,就是一步跳 \(2^k\),可把速度从 \(O(n)\) 直降到 \(O(\log n)\),是一个非常 nice 的算法。
ST 表!
ST 表?这不是最近公共祖先的前置知识?对啊对啊,但这里还是提一嘴吧。
简单东西,只要不修改,可以 \(O(n \log n)\) 预处理然后 \(O(1)\) 查询区间最大值/最小值/最大公因数等。如果要查询和/积这种不能出现重叠部分的内容可以 \(O(\log n)\) 查询,不过一般不怎么用。
预处理核心代码:
void init_MakeST(){
for(int i=2;i<=n;i++)lg[i]=lg[i/2]+1;
for(int i=1;i<=n;i++)st[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)for(int i=1;i<=n-(1<<j)+1;i++)
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
return;
}
查询区间核心代码:
int Answer(int l,int r){
int len=lg[r-l+1];
return max(st[l][len],st[r-(1<<len)+1][len]);
}
很简单,对吧?
可是……矩阵快速幂?
混到倍增里去啦!建好图,跑矩阵快速幂。说白了,矩阵快速幂也是倍增呀,快速幂不就是倍增吗?
用邻接矩阵作为矩阵跑快速幂,很聪明哦。
有一个这样的题目,就是很典型的矩阵快速幂啦!上代码!
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e6+5;
LL n,m,St,Ed,id[N],cnt;
struct Mat{LL a[105][105];}ans,b;
Mat operator * (const Mat &A,const Mat &B){
Mat C;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=cnt;j++)C.a[i][j]=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=cnt;i++)for(int j=1;j<=cnt;j++)
for(int o=1;o<=cnt;o++)C.a[i][j]=min(C.a[i][j],A.a[i][o]+B.a[o][j]);
return C;
}
Mat QP(){n--;ans=b;while(n){if(n&1)ans=ans*b;b=b*b,n>>=1;}return ans;}
int main(){
cin>>n>>m>>St>>Ed;
memset(b.a,0x3f,sizeof(b.a));
for(int i=1;i<=m;i++){
LL x,y,z;cin>>z>>x>>y;
if(!id[x])id[x]=(++cnt);
if(!id[y])id[y]=(++cnt);
b.a[id[x]][id[y]]=z,b.a[id[y]][id[x]]=z;
}
cout<<QP().a[id[St]][id[Ed]];
return 0;
}
居然还有二分!
居然,居然,居然……真没想到,ST 表混进二分了!
好吧也比较正常,毕竟,像区间 GCD 啦,区间最大值啦,区间最小值啦,这些东西,在固定其中一个端点的时候,总是满足单调性的。而这道题目,却是位运算 &。
当然!也满足单调性啦。越 & 肯定越小嘛,但是不排除相等情况,对吧?
好嘞,那么整个一开始构建一个 ST 表,之后给定左边界,直接二分找最大的右边界就好啦!利用 ST 表一开始的预处理,之后可以 \(O(1)\) 计算区间 & 值,然后就没有然后啦!
上!代!码!
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5+5;
LL T,n,a[N],st[N][30],lg[N],Q;
LL C(LL x,LL y){LL k=lg[y-x+1];return (st[x][k]&st[y-(1<<k)+1][k]);}
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)st[i][0]=a[i];
lg[1]=0;for(int i=2;i<=n;i++)lg[i]=lg[i/2]+1;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=st[i][j-1]&st[i+(1<<j-1)][j-1];
cin>>Q;
while(Q--){
LL x,k;cin>>x>>k;
int l=x,r=n,res=-1;
while(l<=r){
int mid=(l+r)/2;
if(C(x,mid)>=k)res=mid,l=mid+1;
else r=mid-1;
}
cout<<res<<" ";
}
cout<<"\n";
}
return 0;
}
诶这个预处理是?
啊啊啊啊啊!ST 表题目又混进预处理了……而且是数论预处理!梦幻。
这这这这这……这拆分太有实力了!
一开始要先拆解质因数,然后要用双指针算法,用数组存储质因数出现个数……太厉害了!然后这里预处理出来的东西才能用到 ST 表里去转移,正好是太神了!
上个代码?
#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const int N = 1e5+5;
LL n,Q,a[N],st[N][30],c[N];
vector<LL> Ft[N];
void init(){
LL Now=0,l=0,r=0;
while(l<=n){
l++,r++;
for(LL u:Ft[l-1]){c[u]--;if(c[u]==1)Now--;}
while(r<=n){
for(LL u:Ft[r]){c[u]++;if(c[u]>1)Now++;}
if(Now!=0){for(LL u:Ft[r]){c[u]--;if(c[u]==1)Now--;}r--;break;}r++;
}
st[l][0]=min(r,n);
}
for(int j=1;j<=20;j++)for(int i=1;i<=n;i++)
st[i][j]=st[min(st[i][j-1]+1,n)][j-1];return;
}
int main(){
cin>>n>>Q;
for(int i=1;i<=n;i++){
cin>>a[i];LL tmp=a[i];
for(LL j=2;j*j<=a[i];j++){
if(tmp%j!=0)continue;
Ft[i].pb(j);while(tmp%j==0)tmp/=j;
}
if(tmp>1)Ft[i].pb(tmp);
}
init();
while(Q--){
LL l,r,sum=0;cin>>l>>r;
for(int i=20;i>=0;i--)
if(st[l][i]<r)l=st[l][i]+1,sum+=(1<<i);
cout<<sum+1<<"\n";
}
return 0;
}
简单概括一下~
ST 表好复杂!其实上它原本不复杂,但套上一大堆乱七八糟的东西,二分啦,矩阵乘法啦,数论啦,双指针啦,之后,就显得特别厉害。整个的思维难度还是杠杠滴!想要掌握倍增,第一步,就是完全打败 ST 表!

浙公网安备 33010602011771号