st算法

1.RMQ问题

先说问题,RMQ(Range Minimum/Maximum Query,即区间最值查询),一个数组随机获取[l,r]范围内的最值,当然如果要获取其它特征的数据也是同理。

2.ST算法

2.1 算法内容

数组a={2,3,6,9,5,0,1};

数组长度n=a.size()=7;

数组dp;

**第一步:用O(nlogn)的时间和空间复杂度的代价创建dp数组。**不过这个dp是相当的难想,dp[i][j]表示从i开始 i + 2 k i+2^k i+2k个数据的最值。这个数据的范围很容易扣不明白边界。比如dp[0][0]表示的是数组a下标从0开始 0 + 2 0 = 1 0+2^0=1 0+20=1个数据的最值,这个“1”是a[0]到a[1]还是只有a[0]自己?a[0]到a[1]是左闭右开还是左闭右闭(a[0]到a[1]的左闭右开和只有a[0]自己不是一个意思,因为当a[0]是最右边界的时候a[1]是不存在的,哪怕结果是一样的,思考方式可不同)?这几个问题当然是写代码的人自己规定的,我说说看我认为最方便的方法,我这里认为dp[0][0]表示的是a[0]自己,就是说加1从自己为0开始加。同理,dp[3][2]表示从a[3]开始 3 + 2 2 = 7 3+2^2=7 3+22=7个数据的最值,即区间 [ 3 , 9 ] [3,9] [3,9]的最值,右侧是闭区间。

**第二步:DP数组的行列数。**按照我上面的规定,basecase很好写,dp的第一列就是数组a的内容,那么行数就固定了。第二个坑来了,那么有几列呢?我特意选了个比较坑的7来判断数组列数。我的第一反应是: ⌈ l o g 2 n ⌉ \lceil log_2 n \rceil log2n,这道题自然 ⌈ l o g 2 7 ⌉ = 3 \lceil log_2 7 \rceil=3 log27=3,但是这就大错特错了。因为第k列表示 i + 2 k i+2^k i+2k,也就是说,这个3不应该是列数,而是最大列数的下标,有第0列的存在,所以列数应该是4,最大列数是3。

**第三步:向dp数组填表。**basecase第一列填好后,$ dp[i][j]=max(dp[i][j-1], dp[i+2^{j-1}, j-1]) $,理解为将dp[i][j]从中间分为两部分,左侧的结果自然就是dp[i][j-1],右侧区间的左边界就是dp[i][j-1]的右边界,因为是将dp[i][j-1]对半分,所以右区间的指数自然和左区间一样是j-1。可见不管是左区间还是右区间,因为纵坐标都是j-1,所以结果都可以通过前一列计算出来,所以填表的时候要按照列填,即第1列填完再填第2列。下面dp[6][1]其实是不存在的,因为a[6]已经是数组的最后一个元素,怎么可能还有 2 j − 1 2^{j-1} 2j1存在呢。有的代码会把这个位置空出来,但是我觉得还是都填上比较好,所以在下面要判断是否越界。

2399
3699
6999
9999
5555
0111
1111

**第四步:查找。**实际查找dp[l][l]的时候,l和r的距离很可能不恰好等于2的某个次方,此时就要比较一次。例如
∃ k , 满 足 l + 2 k < = r ( 其 中 k 为 所 有 可 能 值 中 的 最 大 值 ) , 那 么 d p [ l ] [ r ] = m a x ( d p [ l ] [ k ] , d p [ r − 2 k + 1 ] [ k ] ) {\exist}k,满足l+2^k<=r(其中k为所有可能值中的最大值),那么dp[l][r] = max(dp[l][k], dp[r-2^k+1][k]) k,l+2k<=r(k),dp[l][r]=max(dp[l][k],dp[r2k+1][k])
从右往左数的时候,那个+1很容易落下,我目前没啥好的办法,只能用dp[0][1]来试。

其中这个k的值是 l o g 2 ( r − l + 1 ) log_2 {(r-l+1)} log2(rl+1),这个+1的我也是试出来的。

2.2 代码
#include<iostream>
#include<vector>
#include<math.h>
#include<algorithm>
#include<ctime>
#include<assert.h>
using namespace std;
 
void STpre(vector<int>&a,vector<vector<int> >&b)///左闭右闭
{
    int m = a.size();
    int n = (int)ceil(log(m)/log(2)+1);///<这里是用边界有坑
    vector<vector<int> >dp(m, vector<int>(n,0));
    for(int i=0; i<m; ++i)
        dp[i][0] = a[i];
 
    for(int j=1; j<n; ++j)
    {
        for(int i =0; i<m; ++i)
        {
            auto aa = i+(1<<(j-1))<m ? dp[i+(1<<(j-1))][j-1] : dp[i][j-1];
            dp[i][j] = max(dp[i][j-1], aa);
        }
    }
    swap(b,dp);
//    vector<vector<int> > ().swap(dp);
    return;
}
int STget(vector<vector<int> >dp, int l, int r)///<这里l和r是双闭的
{
    int k = (int)floor(log(r-l+1)/log(2));
    return max(dp[l][k], dp[r-(1<<k)+1][k]);
}
int ST(vector<int>&a, int l, int r)///<左闭右开
{
    assert(!a.empty());///<这个没法处理
    assert(l<=r);
    if(l==r)
        return a[l];
    vector<vector<int> >dp;
    STpre(a, dp);
    return STget(dp, l, r);
}
 
int test(int left,int right,vector<int>&a)
{
    if(left>right || a.empty())
    {
        printf("a\n");
        return -1;
    }
    return *max_element(a.begin()+left, a.begin()+right+1);
}
void printa(vector<int>&a)
{
    for(size_t i=0;i<a.size();++i)
        cout<<a[i]<<",";
    cout<<endl;
}
int main(void)
{
    srand((unsigned)time(NULL));
    for(int i=0;i<10000;++i)
    {
        int len = 10000;///<len是个数上限
        int n = rand()%(len-1-1+1)+1;///<1~len-1,n是实际个数
        vector<int>a;
        for(int j=0;j<n;++j)
            a.push_back(rand()%(1000-0+1)+0);
 
        int left = rand()%(n-1-0+1)-0;///<0~n-1
        int right;
        if(left != n)
            right = rand()%(n-1-left+1)+left;///<left~n-1
        else
            right = left;
        int y=test(left,right,a);
 
        int x=ST(a, left, right);
        if(x!=y)
        {
            cout<<"left="<<left<<",right="<<right<<endl;
            printa(a);
            cout<<"st="<<x<<",test="<<y<<",n="<<n<<endl;
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tux~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值