RMQ问题
RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干次询问RMQ(i,j),返回数列A中下标在区间[i,j]中的最小/大值。
ST算法
ST(Sparse Table,稀疏表,ST表)是用来解决RMQ问题的一种算法,预处理复杂度O(nlogn),查询复杂度O(1),不支持在线修改后查询。
构造
ST表的构造采用了动态规划的思想,
状态表示:
s
t
[
i
]
[
j
]
=
m
a
x
k
=
j
j
+
2
i
−
1
a
[
k
]
st[i][j] = max_{k=j}^{j+2^i-1}a[k]
st[i][j]=maxk=jj+2i−1a[k],即从j到j+2^i-1的最大(或最小)值。
初始状态:
s
t
[
0
]
[
j
]
=
a
[
j
]
st[0][j] = a[j]
st[0][j]=a[j]
状态转移:
s
t
[
i
]
[
j
]
=
m
a
x
(
s
t
[
i
−
1
]
[
j
]
,
s
t
[
i
−
1
]
[
j
+
2
i
−
1
]
)
st[i][j] = max(st[i-1][j], st[i-1][j+2^{i-1}])
st[i][j]=max(st[i−1][j],st[i−1][j+2i−1])
i
取
1
到
l
o
g
n
,
j
取
1
到
n
i取1到logn,j取1到n
i取1到logn,j取1到n,一共
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)
查询
我们已经知道以任意一点开始了长度为2的幂次的区间最大值,如果要求长度为L的[x,y]区间内的最大值,就可以求[x,x+k],[y-k+1,y]这两个区间最大值的最大值,其中k是小于L的最大2的幂次。
即
m
a
x
(
s
t
[
t
]
[
x
]
,
s
t
[
t
]
[
y
−
2
t
+
1
]
)
,
其
中
t
=
l
o
g
2
(
y
−
x
+
1
)
max(st[t][x],st[t][y-2^t+1]),其中t=log2(y-x+1)
max(st[t][x],st[t][y−2t+1]),其中t=log2(y−x+1)
log函数可以预处理一个数组,每次查询复杂度
O
(
1
)
O(1)
O(1)
代码
学习了一些C++类的知识,静态成员必须在类外定义及初始化,在类中只是声明,且在类外用双冒号表示这是一个类的静态成员。
另外,vector初始化第一个参数表示大小,第二个表示初始值,所以二维vector的初始化方式:vector<vector<int>> vvi(outsize, vector<int>(insize))
(9月27日改)使用std::function实现了自定义比较函数。
class SpraseTable
{
static int lg[M];
int n;
function<int(int,int)> cmp;
vector<vector<int>> table; //table[i][j]表示长度为2^i的以j开头的数组最值
public:
SpraseTable(int *arr, int _n,
function<int(int,int)> _cmp = [](int a,int b){return a<b?a:b;}
) : n(_n), cmp(_cmp)
{
if(!lg[0]) {lg[0]=-1;for(int i=1;i<M;i++)lg[i]=lg[i/2]+1;}
table = vector<vector<int>>(lg[n] + 1, vector<int>(n + 1));
for(int i = 1; i <= n; i++)
table[0][i] = src[i];
for(int i = 1; i <= lg[n]; i++)
for(int j = 1; j <= n; j++)
if(j + (1 << i) - 1 <= n)
table[i][j] = cmp(table[i-1][j], table[i-1][j+(1<<(i-1))]);
}
inline int query(int x, int y)
{
t = lg[y - x + 1];
return cmp(table[t][x], table[t][y - (1 << t) + 1]);
}
};int SpraseTable::lg[M];
模板题
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
inline int read(); inline void write(int x);
const int M = 100016, MOD = 1000000007;
class SpraseTable
{
static int lg[M];
int n;
function<int(int,int)> cmp;
vector<vector<int>> table; //table[i][j]表示长度为2^i的以j开头的数组最值
public:
SpraseTable(int *arr, int _n,
function<int(int,int)> _cmp = [](int a,int b){return a<b?a:b;}
) : n(_n), cmp(_cmp)
{
if(!lg[0]) {lg[0]=-1;for(int i=1;i<M;i++)lg[i]=lg[i/2]+1;}
table = vector<vector<int>>(lg[n] + 1, vector<int>(n + 1));
for(int i = 1; i <= n; i++)
table[0][i] = arr[i];
for(int i = 1; i <= lg[n]; i++)
for(int j = 1; j <= n; j++)
if(j + (1 << i) - 1 <= n)
table[i][j] = cmp(table[i-1][j], table[i-1][j+(1<<(i-1))]);
}
inline int query(int x, int y)
{
int t = lg[y - x + 1];
return cmp(table[t][x], table[t][y - (1 << t) + 1]);
}
};int SpraseTable::lg[M];
int save[M];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n=read(),m=read();
for(int i=1;i<=n;i++)
save[i] = read();
SpraseTable spt = SpraseTable(save,n,[](int a,int b){return a>b?a:b;});
while(m--)
{
int a = read(),b=read();
printf("%d\n",spt.query(a,b));
}
return 0;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
其他
RMQ可以用来求LCA,下次再细学.
(9月27日补)已学,参见【笔记】dfs序,欧拉序,LCA的RMQ解法
ST表维护的运算需要满足什么性质?首先肯定是半群上的,然后为什么这种做法不能用来求区间和?