倍增、ST、RMQ

本文深入解析了倍增、ST状态压缩与RMQ快速查询算法,通过实例POJ3264、3368、HDU3486和POJ2019,展示了如何在不同场景中利用这些算法解决区间最值问题,包括奶牛高度差异、最频繁值查询、面试官分组和矩阵最值差计算。
摘要由CSDN通过智能技术生成

算法思想

倍增

状态空间过大,使用倍增思想,只使用2的整数次幂,例如查找树上祖先,将x与当前节点向上2i个节点比较,若x大于该节点,向上跳2i,加大增量2i+1,小于则减少增量2i-1,继续比较直到相等

ST

F [ i , j ] F[i,j] F[i,j] [ i , 2 j − 1 + i ] [i,2^j-1+i] [i,2j1+i]范围内的最值,区间长2j,根据倍增思想,将2j分成两个2j-1,分别求子区间最值,然后求统一最值,可得状态转移方程: F [ i , j ] = m a x ( F [ i , j − 1 ] , F [ i + 2 j − 1 , j − 1 ] ) F[i,j]=max(F[i,j-1],F[i+2^{j-1},j-1]) F[i,j]=max(F[i,j1],F[i+2j1,j1]),j的取值被限定在 1 − l o g 2 ( N ) 1-log2(N) 1log2(N),N为总区间长度,构造表的代码如下

for(int i=1;i<=N;i++)
	F[i][0]=a[i];
for(int j=1;j<=log2(N);j++)
	for(int i=1;i<=n-(1<<j)+1;i++)//n-(1<<j)+1是为了确保能取到边界值
		F[i][j]=max(F[i][j-1],F[i+(1<<(j-1)][j-1]);

构造完表之后需要对其进行查询操作,假设查询区间为[l,r],计算最多所需要的二次幂为k,区间长度为r-l+1,k=log2(r-l+1),当查询区间属于2k到2k+1,将查询区间分成两个区间,取总区间最值即可,两区间分别为l向后2k个数以及r向前2k个数,区间存在重叠,但是由于是求最值,所以重叠无影响,代码如下

int RMQ(int l,int r)
{
	int k=log2(r-l+1);
	return max(F[l][k],F[r-(1<<k)+1][k]);
}

RMQ

区间最值查询,有多重解决方式,是一类问题的总称,线段树处理时间为O(nlogn),查询时间为O(logn),支持在线修改,ST预处理时间为O(nlogn),查询时间为O(1),不支持在线修改

训练

POJ3264

题目大意:N头奶牛,每只有自己的高度,Q次查询,每次给出一个范围,求出范围内最值差

思路:创建ST,直接查询区间内两个最值,输出差值

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int data[50001],N,Q,Fmax[50001][30],Fmin[50001][30];
void ST() {
    for(int i=1; i<=N; i++)
        Fmax[i][0]=Fmin[i][0]=data[i];
    for(int j=1; j<=log2(N); j++)
        for(int i=1; i<=N-(1<<j)+1; i++) {//因为求的是最值差,所以需要存两个
            Fmax[i][j]=max(Fmax[i][j-1],Fmax[i+(1<<(j-1))][j-1]);
            Fmin[i][j]=min(Fmin[i][j-1],Fmin[i+(1<<(j-1))][j-1]);
        }
}
int RMQ(int a,int b) {//获得最值
    int k=log2(b-a+1);
    return max(Fmax[a][k],Fmax[b-(1<<k)+1][k])-min(Fmin[a][k],Fmin[b-(1<<k)+1][k]);
}
int main() {
    scanf("%d%d",&N,&Q);
    for(int i=1; i<=N; i++)
        scanf("%d",&data[i]);
    ST();
    while(Q--) {
        int A,B;
        scanf("%d%d",&A,&B);
        printf("%d\n",RMQ(A,B));
    }
    return 0;
}

POJ3368

题目大意:给出n各整数的非递减序列,对每个索引i和j组成的查询,确定范围内的最频繁值(出现次数最多的值)

思路:使用F[i,j]表示 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]内的最值,F[i,0]记录的是从1-i连续重复数字对应的次数,注意,只有F[i,0]是记录次数的,F内的其他元素实际上是为了求得数值的最大值的操作结果,查询l~r内最大值,若第l个数与前一个数相等,则先统计第l个数在查询区间内的出现次数,再查询剩余区间最大值,具体如图

在这里插入图片描述

对于本题RMQ
1.F[l]所得的其实为对应的t[l]值,即当前连续数字(data[l])在1~l出现次数
2.F[l]-F[x],data[x]=data[l],对应data[l]值在l~x出现次数,即x-l+1
3.F[x+1]-F[r],大于data[l]值各自出现的次数

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,F[121212][20],Ib[121212]= {-1},a[121212],q,x,y;
void ST() {
    for(int i=1; i<=n; i++)
        if(a[i]==a[i-1]&&i!=1)
            F[i][0]=F[i-1][0]+1;
        else
            F[i][0]=1;
    for(int j=1; j<=Ib[n]; j++)
        for(int i=1; i<=n-(1<<j)+1; i++)
            F[i][j]=max(F[i][j-1],F[i+(1<<(j-1))][j-1]);
}
int RMQ() {
    if(x>y)return 0;
    int t=x,k;
    while(a[t]==a[t-1]&&t<=y)//去掉左边重复值
        t++;
    k=Ib[y-t+1];//获得区间长度对应的log2值
    return max(t-x,max(F[t][k],F[y-(1<<k)+1][k]));//获得最值,注意坐标第一位的取值
}
int main() {
    for(int i=1; i<=100000; i++)//预处理范围内的log2值
        Ib[i]=(i&(i-1))?Ib[i-1]:Ib[i-1]+1;
    while(~scanf("%d",&n)&&n) {
        scanf("%d",&q);
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        ST();
        while(q--) {
            scanf("%d%d",&x,&y);
            printf("%d\n",RMQ());
        }
    }
    return 0;
}

HDU3486

题目大意:n个人面试,需要聘请m个人,选择m个面试官,把n个人分成长度为n/m的m段,对每一段取能力值最大,希望m尽量小,并且所得的总能力值大于k,求最小m

思路:尝试每个m,判断是否能得到总和大于k的值,用二分的方式来尝试m,如果所有能力值之和小于k,代表无m

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int n,k,data[212121],F[212121][20];
void ST() {
    for(int j=1; j<=log2(n); j++)
        for(int i=1; i<=n-(1<<j)+1; i++)
            F[i][j]=max(F[i][j-1],F[i+(1<<(j-1))][j-1]);
}
int RMQ(int l,int r) {
    if(r<l)
        return 0;
    int k=log2(r-l+1);
    return max(F[l][k],F[r-(1<<k)+1][k]);
}
bool Judge(int m) {
    int sum=0,t=n/m;
    for(int i=0; i<m; i++)//获得分组后的和
        sum+=RMQ(t*i+1,t*i+t);//隔t取,重要
    return sum>k;
}
int main() {
    while(~scanf("%d%d",&n,&k)&&n!=-1&&k!=-1) {
        int sum=0;
        for(int i=1; i<=n; i++) {
            scanf("%d",&data[i]);
            sum+=data[i];
        }
        if(sum<k) {
            printf("-1\n");
            continue;
        }
        for(int i=1; i<=n; i++)
            F[i][0]=data[i];
        ST();
        int l=1,r=n,ans=0;
        while(l<=r) {//二分获得最佳分组数
            int m=(l+r)/2;
            if(Judge(m))
                r=m-1,ans=m;
            else
                l=m+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

POJ2019

题目大意:N×N的矩阵,每个矩阵有正整数值,有K组查询,整数B为所取的查询大小,每组查询给出左上角坐标,求B×B矩阵中的最值差

思路:二维ST表,注意坐标的相对关系与取值,需要开两个矩阵存最大与最小

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;
int N,K,B,Fmax[500][500][20],Fmin[500][500][20],data[251][251];
void ST() {
    for(int i=1; i<=N; i++)
        for(int j=1; j<=N; j++)
            Fmax[i][j][0]=Fmin[i][j][0]=data[i][j];
    for(int k=1; k<=N; k++)//行
        for(int j=1; j<=log2(N); j++)
            for(int i=1; i<=N-(1<<j)+1; i++) {//范围
                Fmax[k][i][j]=max(Fmax[k][i][j-1],Fmax[k][i+(1<<(j-1))][j-1]);
                Fmin[k][i][j]=min(Fmin[k][i][j-1],Fmin[k][i+(1<<(j-1))][j-1]);
            }
}
int RMQ(int x,int y) {
    int maxx=-1,minn=999999999,k=log2(B),l=y,r=y+B-1;
    for(int i=x; i<x+B; i++) {//对每行求最值
        maxx=max(maxx,max(Fmax[i][l][k],Fmax[i][r-(1<<k)+1][k]));
        minn=min(minn,min(Fmin[i][l][k],Fmin[i][r-(1<<k)+1][k]));
    }
    return maxx-minn;
}
int main() {
    scanf("%d%d%d",&N,&B,&K);
    for(int i=1; i<=N; i++)
        for(int j=1; j<=N; j++)
            scanf("%d",&data[i][j]);
    ST();
    while(K--) {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",RMQ(x,y));
    }
    return 0;
}

总结

这一讲主要是学会ST表的使用,倍增是一种广泛使用的思想,RMQ是一类问题的总称,ST表的关键是建表和查询,对二次幂的使用是ST实现的关键

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值