zoukankan      html  css  js  c++  java
  • ST表学习笔记

    经过了树状数组的折磨学习,小蒟蒻(hqk)又学了一个新的结构——(ST)表(其实应该是(ST)算法,因为(T)本来就有(table)的意思,但是由于大家都这么叫,接下来的文章里也沿用这一说法。

    (ST)

    (1))区间(RMQ)问题

    区间(RMQ)($ Rangespace Maximum/minimumspace Query ()问题,顾名思义,就是询问某个区间的最大最小值。这种问题通常有很多种解法,比如线段树、树状数组,还有像笛卡尔树、莫队这样的神仙做法。但是我们今天要探讨的解法是一种比较好理解的算法——)ST$表


    (2))啥是(ST)

    (ST)表是基于动态规划的一种算法。为啥叫表呢?是因为(ST)表在运行过程中是先进行预处理,然后进行查询。这种算法能够实现(O(nlogn))预处理,(O(1))查询。是一种比较高效的算法,但是需要注意的一点是,这种算法的空间复杂度较高,需要一些优化(或者使用其他的算法来代替)才能通过一些毒瘤题目


    (3)(ST)表的基本思想

    其实,(ST)表的基本思想就是(dp)

    我们用(a[1···n])表示一组数。设(f[i][j])表示从(a[i])加到(a[i+2^i-1])这个范围内的最大值,也就是说(f[i][j])表示以(a[i])为起点连续(2^i)个数的最大值。由于元素个数为(2^j)个,所以我们可以考虑分治的思想,分而治之,分别求出左半边((2^{j-1}))的最大值,再求右半边的最大值,即(f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]))从前往后扫描一下就可以预处理出来。

    接下来我们要考虑如何进行查询

    每提问一个区间([l,r]),一定会存在一个数(x),使得(2^xleq r-l+1)。只要求出了这个值,我们就可以用已经与处理完毕的(f[][])来进行回答了。

    具体方法是:(min(f[l][x],f[r-2^x+1][x]))这个东西可以再(O(1))的时间内求出来

    等等,怎么求(x)呢?**

    其实,求(x)的方法也很简单,就是(log_2^{r-l+1}),具体是为什么需要读者自己去思考,这里就不再赘述了

    但是怎么求(log_2^{r-l+1})呢?

    我们可以维护一个(log[]),其中(log[i])表示(log_2^i)。至于(log[i])的计算,我们可以用下面一个递推式:(log[i]=log[i/2]+1);不过如果再懒一点的话可以调用(cmath)库里的(log2)函数


    (4)(ST)表的例题

    其实就是(ST)表的实现

    例题((1)

    P3865 【模板】ST表

    题目背景

    这是一道(ST)表经典题——静态区间最大值

    请注意最大数据时限只有(0.8s),数据强度不低,请务必保证你的每次查询复杂度为$ O(1)$

    题目描述

    给定一个长度为$ N (的数列,和) M $次询问,求出每一次询问的区间内数字的最大值。

    输入输出格式

    输入格式:

    第一行包含两个整数$ N, M$,分别表示数列的长度和询问的个数。

    第二行包含(N)个整数(记为 (a_i)),依次表示数列的第$ i$项。

    接下来$ M$行,每行包含两个整数 (l_i, r_i),表示查询的区间为$ l_i, r_i$

    输出格式:

    输出包含$ M$行,每行一个整数,依次表示每一次询问的结果。

    题解

    这就是一道板子题啊!莫慌莫慌,都在代码里了——

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    const int N=1e6+5,logn=20;
    
    int log[N],f[N][logn+5],a[N];
    int n,m,x,y;
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	log[0]=-1;//一定要注意这个小细节,才能使log[1]=0
    	for(int i=1;i<=n;++i)
    	{
    		f[i][0]=a[i];//将形如[i,i]的都标作a[i],作为dp的边界条件
    		log[i]=log[i>>1]+1;//对log的处理
    	}
    	for(int j=1;j<=logn;++j)//外循环是1~logn
    		for(int i=1;i+(1<<j)-1<=n;++i)//内循环是一直到出界
    			f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//套进刚才的式子中
    	while(m--)//循环读入数据
    	{
    		scanf("%d%d",&x,&y);
    		int s=log[y-x+1];
    		printf("%d
    ",max(f[x][s],f[y-(1<<s)+1][s]));//这些式子我们都进行过说明,这里就不说了
    	}
    	return 0;
    }
    

    例题((2)

    P2251 质量检测

    题目背景

    题目描述

    为了检测生产流水线上总共(N)件产品的质量,我们首先给每一件产品打一个分数(A_i)表示其品质,然后统计前M件产品中质量最差的产品的分值(Q_m = min(A_1, A_2, ... A_m)),以及第(2)至第(M + 1)件的(Q_{m + 1}, Q_{m + 2} ...) 最后统计第(N - M + 1)至第(N)件的(Q_n)。根据(Q)再做进一步评估。

    请你尽快求出(Q)序列。

    输入输出格式

    输入格式:

    输入共两行。

    第一行共两个数(N、M),由空格隔开。含义如前述。

    第二行共(N)个数,表示(N)件产品的质量。

    输出格式:

    输出共(N - M + 1)行。

    (1)(N - M + 1)行每行一个数,第(i)行的数(Q_{i + M - 1})。含义如前述。

    题解

    其实这道题也算一道模板题,只不过是将询问变成了让你自己循环跑一边,不过要注意的是,一定要将(m-1)后再进行循环

    代码实现:

    #include<cstdio>
    #include<iostream>
    
    using namespace std;
    
    const int maxn=2000010;
    const int logn=20;
    
    int log[maxn],f[maxn][logn],a[maxn];
    int n,m;
    
    void init()
    {
    	scanf("%d%d",&n,&m);
    	log[0]=-1;
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%d",&a[i]);
    		f[i][0]=a[i];
    		log[i]=log[i>>1]+1;
    	}
    	for(int j=1;j<=logn;++j)
    		for(int i=1;i+(1<<j)-1<=n;++i)
    			f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
    }
    
    void work()
    {
    	m-=1;
    	for(int i=1;i+m<=n;++i)
    	{
    		int s=log[m+1];
    		printf("%d
    ",min(f[i][s],f[i+m-(1<<s)+1][s]));
    	}
    }
    
    int main()
    {
    	init();
    	work();
    	return 0;
    }
    
  • 相关阅读:
    Java 开发环境配置
    JDBC数据批处理
    knockout.js简单实用教程1
    angular 入门教程1
    knockout简单实用教程3
    knockout简单实用教程2
    autofac使用笔记
    MVC WEB api 自动生成文档
    Unity IOC注入详细配置(MVC,WebApi)
    为什么php往mongo里插入整型数字8,变成了numberint(8)
  • 原文地址:https://www.cnblogs.com/juruohqk/p/11105756.html
Copyright © 2011-2022 走看看