ST学习小结

算法介绍

RMQ问题
给定数组A[0, N-1],找出给定的两个索引i和j间的最小值的位置。
ST(Sqarse Table)算法
是利用倍增的思想来解决RMQ问题的算法之一,适用于查询次数很大。
可用于得到区间的最小值和最大值。
用二维数组M[i][j]表示从下标i开始,长度为 2 j 2^{j} 2j的子数组的最小值的索引。
算法复杂度: 初始化 O ( n l o g n ) O(nlogn) O(nlogn) 和查询 O ( n ) O(n) O(n)

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define T int T; scanf("%d", &T); while(T--)
typedef long long ll;
const int N=1e5+10;
const int M=50;
int n, a[N], b[N], stmin[N][M], stmax[N][M];

void init(){
	b[0]=-1;//b[length]=j; length=2^j;
	for(int i=1; i<=n; i++){
		b[i] = ((i)&(i-1)==0) ? b[i-1]+1 : b[i-1];
		stmax[i][0]=a[i];
		stmin[i][0]=a[i];
	}

	for(int j=1; j<=b[n]; j++){
		for(int i=1; i+(1<<j)-1<=n; i++){
			stmax[i][j]=max(stmax[i][j-1], stmax[i+(1<<(j-1))][j-1]);
			stmin[i][j]=min(stmin[i][j-1], stmin[i+(1<<(j-1))][j-1]);
		}
	}
}

int rmq_max(int l, int r){
	int k=b[r-l+1];
	return max(stmax[l][k], stmax[r-(1<<k)+1][k]);
}

int rmq_min(int l, int r){
	int k=b[r-l+1];
	return min(stmin[l][k], stmin[r-(1<<k)+1][k]);
}

int main()
{
	scanf("%d", &n);
	for(int i=1; i<=n; i++){
		scanf("%d", &a[i]);
	}

	init();
    int q, a, b;
    scanf("%d", &q);
    while(q--){
    	scanf("%d%d", &a, &b);
    	printf("%d %d\n", rmq_max(a, b), rmq_min(a, b));
    }
    
	return 0;
}

练习(补题

来一道题目练一练(其实是补题啦)
题目
这道题除了ST算法,还有双指针。
当某个区间的最大值与最小值之差已经大于k,那么固定i,j以后的区间都满足条件。然后i的位置再增加。此时为什么j不从1开始。假设j因为i位置的改变而从1开始,此时i和j的区间范围内,已经遍历过一遍。所以j没有必要从1开始。(这就是双指针)。如果一直没有满足差值大于k,则j一直增加j。

#include <bits/stdc++.h>
using namespace std;
#define T int T; scanf("%d", &T); while(T--)
typedef long long ll;
const int N=1e5+10;
const int M=25;
int n, m, a, b[N], st1[N][M], st2[N][M];

void init(){
    b[0]=-1;
    for(int i=1; i<=n; i++){
        b[i]=(((i)&(i-1))==0) ? b[i-1]+1 : b[i-1];
       
    }

    for(int j=1; j<=b[n]; j++){
        for(int i=1; i<=n-(1<<j)+1; i++){
            st1[i][j]=min(st1[i][j-1], st1[i+(1<<(j-1))][j-1]);
            st2[i][j]=max(st2[i][j-1], st2[i+(1<<(j-1))][j-1]);
        }
    }
}

bool diff(int l, int r, ll k){
    int index=b[r-l+1];
    int x=max(st2[l][index], st2[r-(1<<index)+1][index]);
    int y=min(st1[l][index], st1[r-(1<<index)+1][index]);

    if(x-y>k)
        return true;
    else
        return false;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++){ 
        scanf("%d", &a);
        st1[i][0]=a;
        st2[i][0]=a; 
    }

    init();
    ll k, ans;

    while(m--){
        ans=0;
        scanf("%lld", &k);

        for(int i=1, j=1; i<=n; i++){
            while(!diff(i, j, k) && j<=n && i<=j){
                j++;
            }

            if(j<=n)
                ans+=n-j+1;
        }

        printf("%lld\n", ans);
    }
    
    return 0;
}

拓展

倍增思想还可用于解决LCA问题
当然,还是树链剖分更好

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int LOGMAXN=50;

int N, father[MAXN], P[MAXN][LOGMAXN], L[MAXN];
void preProcess(int N){//预处理
	for(int i=0; i<N; i++){//对二维数组P进行初始化
		for(int j=0; (1<<j)<N; j++){
			P[i][j]=-1;
		}
	}

	for(int i=0; i<N; i++)//路径为0时, P[i][0]为每一个叶结点
		P[i][0]=father[i];

	for(int j=1; (1<<j)<N; j++){//每个子结点分别想下跳出2^j的距离
		for(int i=0; i<N; i++){
			if(P[i][j-1]!=-1)
				P[i][j]=P[P[i][j-1]][j-1];
		}
	}
}

int LCA(int u, int v){
	int tmp, log, i;
	if(L[u]<L[v])//使结点u为深度最深的结点
		swap(u, v);

	for(log=1; (1<<log)<=L[u]; log++);//得到从u到子结点的最大2^(j+1)

	log--;
 
    for(i=log; i>=0; i--){//使得u和v处于同一层
    	if(L[u]-(1<<i)>=L[v])
    		u=P[u][i];
    }

    if(u==v)//如果u恰好为v的father
    	return u;

    for(i=log; i>=0; i--){//每次挑选能走的最大2^i
    	if(P[u][i]!=-1 && P[u][i]!=P[v][i]){//防止跳出范围
    		u=P[u][i];
    		v=P[v][i];
    	}
    }

    //最终的结果为距离lca(u, v)一步之遥,所以返回father[u]
    return father[u];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值