【学习笔记】进阶算法——倍增

什么是倍增?

倍增?倍增?倍增!

之前学了最近公共祖先 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 表!

posted @ 2025-08-09 11:08  嘎嘎喵  阅读(33)  评论(0)    收藏  举报