ST表讲解与例题

ST(Sparse Table) 算法

简介

  • 适用范围
    ST 表是用于解决可重复贡献问题的的区间询问的数据结构,基本思想是倍增和DP
    所谓可重复贡献问题,即运算具有自反性的问题,例如求最大/小值,最大公约数,最小公倍数 x = m a x ( x , x ) = m i n ( x , x ) = g c d ( x , x ) = l c m ( x , x ) x=max(x,x)=min(x,x)=gcd(x,x)=lcm(x,x) x=max(x,x)=min(x,x)=gcd(x,x)=lcmx,x(都可归为RMQ(Range Minimum/Maximum Query)问题)等,而像求和就不具有自反性,所以不能用ST表解决。

  • ST算法基本描述
    通过状态转移方程预处理答案后,对于每个询问的区间将其拆分成两个叠加后将询问区间刚好完全覆盖的区间,然后取两个区间的运算结果作为询问区间的结果,所拆分成的两个区间可以有重叠部分(由于处理的是可重复贡献问题,所以没有影响)。

  • 具体实现

    1、用一个二维数组 S T [ i ] [ j ] ST[i][j] ST[i][j]表示区间 【 i , i + 2 J − 1 】 【i,i+2^J-1】 i,i+2J1的答案。

    2、根据状态转移方程,预处理出ST数组,即 S T [ i ] [ j ] = o p t ( S T [ i ] [ , j − 1 ] , S T [ i + 2 J − 1 ] [ j − 1 ] ) ST[i][j]=opt(ST[i][,j-1],ST[i+2^{J-1}][j-1]) ST[i][j]=opt(ST[i][,j1],ST[i+2J1][j1])

    3、对于询问区间 【 l , r 】 【l,r】 l,r,则其答案 a n s = o p t ( S T [ l ] [ l + 2 s ] , S T [ r − 2 s + 1 ] [ r ] ) ans=opt(ST[l][l+2^s] , ST[r-2^s+1][r]) ans=opt(ST[l][l+2s],ST[r2s+1][r]),其中 s = l o g 2 ( r − l + 1 ) s=log_2(r-l+1) s=log2(rl+1)
    为什么是拆分成这个两个区间,因为这两个区间合并后一定刚好完全覆盖询问区间。

  • 复杂度分析
    需要 O ( n l o g n ) O(nlogn) O(nlogn)的时间复杂度和空间复杂度进行需处理,然后对于每个询问只需要 O ( 1 ) O(1) O(1)的复杂度获得答案。所以总的复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

例题

例题一:洛谷3865
模板题。
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
const int logn = 22;
const int maxn = 2000005;
int f[maxn][logn], Logn[maxn]; 
int n, m, x, y;
void pre()
{
    //预处理log函数
    Logn[1] = 0;Logn[2] = 1;
    for (int i = 3; i < maxn; i++)
        Logn[i] = Logn[i >> 1] + 1;
    //递推
    for (int j = 1; j <= logn; j++)
        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]);
}
int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> f[i][0];
    pre();
    for (int i = 1; i <= m; i++)
    {
        cin >> x >> y;
        int s = Logn[y - x + 1];
        int ans=max(f[x][s], f[y - (1 << s) + 1][s]);
        cout<<ans<<'\n';
    }
    return 0;
}

例题二:毒瘤题
在这里插入图片描述

这题我调了几个小时也没调出来,主要是他的判定有点恶心,希望有同学做出来以后给我发一下。
简单说一下思路:开两个ST数组, S T M I N STMIN STMIN S T M A X STMAX STMAX,一个存最大值,一个存最小值。将数组初始化为-1,完成输入后预处理出两个ST数组。由于初始化是-1,所以 S T M I N [ i ] [ j ] = = − 1 STMIN[i][j]==-1 STMIN[i][j]==1则表示该区间有未知的年份。然后根据题意输出结果即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的类是一种自定义数据类型,它可以封装数据和方法,实现代码的重用和组织。类的定义通常包含在头文件中,并且需要在源文件中实现它的方法。 以下是一个简单的类的定义和使用的例子: ```c++ #include <iostream> using namespace std; class Person { private: string name; int age; public: void setName(string n) { name = n; } void setAge(int a) { age = a; } void print() { cout << "Name: " << name << endl; cout << "Age: " << age << endl; } }; int main() { Person p1; p1.setName("Alice"); p1.setAge(25); p1.print(); return 0; } ``` 这个类叫做Person,它有两个私有成员变量name和age,以及三个公有成员函数setName、setAge和print。setName和setAge用于设置name和age的值,而print函数用于打印这些值。 在main函数中,我们创建了一个Person对象p1,并调用了它的setName、setAge和print函数,以设置和打印name和age的值。 除了上面的例子,C++中的类还可以包含构造函数和析构函数、静态成员、继承等特性。以下是一个包含构造函数和析构函数的例子: ```c++ #include <iostream> using namespace std; class MyClass { private: int *p; public: MyClass(int x) { p = new int; *p = x; cout << "Constructor called" << endl; } ~MyClass() { delete p; cout << "Destructor called" << endl; } void print() { cout << "Value: " << *p << endl; } }; int main() { MyClass obj(10); obj.print(); return 0; } ``` 这个类叫做MyClass,它有一个私有成员变量p,代一个整数指针。构造函数MyClass(int x)用于创建一个动态分配的整数,并将其赋值给p;析构函数~MyClass()用于释放p所指向的内存。print函数用于打印p所指向的整数的值。 在main函数中,我们创建了一个MyClass对象obj,并传入参数10。然后调用了它的print函数,以打印p所指向的整数的值。最后,在程序结束时,析构函数会被自动调用,释放p所指向的内存。 这仅仅是C++中类的一些基础概念和例子。如果想要更深入地了解,建议多阅读一些相关资料和书籍,进行实践和练习。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值