ST表是用来求解区间最大值的一种优秀的离线算法,它可以 O ( n l o g n ) O(nlogn) O(nlogn)预处理,,然后O(1)查询,如何实现呢?
运用了近似于区间DP的方法,一个大区间有小区间转移得到,不同的是,我们定义 S T [ i ] [ k ] ST[i][k] ST[i][k]表示从第 i i i个位置起, 2 k 2^k 2k个数的最大值,咋转移呢?发现我们只处理 2 k 2^k 2k所以 2 k 2^k 2k可以拆成两个没有相交的长度为 2 k − 1 2^{k-1} 2k−1的区间
分别是 S T [ i ] [ k − 1 ] ST[i][k-1] ST[i][k−1]和 S T [ i + 2 k − 1 ] [ i − 1 ] ST[i+2^{k-1}][i-1] ST[i+2k−1][i−1],也就是说我们需要先预处理这两个小区间的值,而这两个小区间需要更小的区间来更新,所以到底层就是 S T [ i ] [ 0 ] ST[i][0] ST[i][0]也就是每个位置上的原数了,然后再一层一层的向上贡献,至此实现方法已经有了
//预处理
inline void pre()
{
for (int i=1;i<=e;i++)
for (int k=1;k<=n-(1<<i)+1;k++)
f[k][i]=max(f[k][i-1],f[k+(1<<(i-1))][i-1]);
return ;
}
我们外层枚举 2 k 2^k 2k,内层循环每个位置,然后每次拆成上面两个式子的max就好了
//询问
inline int que(int l,int r)
{
int t=(log2(r-l+1));
return max(f[l][t],f[r-(1<<t)+1][t]);
}
发现询问区间有两种情况
1. 1. 1.是 2 k 2^k 2k,那么直接对区间长取个 l o g 2 ( r − l + 1 ) log_2({r-l+1}) log2(r−l+1)然后输出 S T [ l ] l o g ] ST[l]log] ST[l]log]就行了
2. 2. 2.不是 2 k 2^k 2k,那么不难发现,他可以拆成两个 2 k < l e n 2^k<len 2k<len的区间的并,也就是说从区间左端点向右一段不超过区间长,但大于 1 2 l e n \frac{1}{2}len 21len的区间,从右端点向左一段不超过区间长,但大于 1 2 l e n \frac{1}{2}len 21len的区间,发现都过了 1 2 \frac{1}{2} 21所以肯定会有交集,也肯定将 整个区间覆盖全了,然后直接对这两部分取max就好
然后我们发现,len是 2 k 2^k 2k就会分成两个完全覆盖区间的相同的区间,所以就可以合并这两种情况了,输出上面代码里的式子就好了
定义 S T [ i ] [ k ] [ j ] [ e ] ST[i][k][j][e] ST[i][k][j][e]表示从位置 ( i , k ) (i,k) (i,k)向右 2 j 2^j 2j,向下 2 e 2^e 2e这个矩形的最大值,然后转移的话类比一下一维,分成4个小矩形就好了,只不过分的方式不同,具体看代码吧
//预处理
inline void pre()
{
int t=log2(n),e=log2(m);
for (int i=0;i<=t;i++)
for (int k=0;k<=e;k++)
{
if (i==0&&k==0) continue;
for (int j=1;j<=n-(1<<i)+1;j++)
for (int p=1;p<=m-(1<<k)+1;p++)
if (i==0)
f[j][p][i][k]=max(f[j][p][i][k-1],f[j][p+(1<<(k-1))][i][k-1]);
else
f[j][p][i][k]=max(f[j][p][i-1][k],f[j+(1<<(i-1))][p][i-1][k]);
}
return ;
}
自己意会一下分割方式,脑补一下过程
//查询
inline int que(int x,int y,int l,int r)
{
int t=log2(l-x+1);
int e=log2(r-y+1);
int a1=max(f[x][y][t][e],f[l-(1<<t)+1][r-(1<<e)+1][t][e]);
int a2=max(f[l-(1<<t)+1][y][t][e],f[x][r-(1<<e)+1][t][e]);
return max(a1,a2);
}
这也是个神奇的过程,继续脑补
若需要在线,可以用树状数组或者线段树
二维ST表还有另一种写法,可以少一个log,具体实现就是 S T [ i ] [ j ] [ k ] ST[i][j][k] ST[i][j][k]表示以 ( i , j ) (i,j) (i,j)为起点 2 k 2^k 2k为边长的正方形的最大值,询问需要费点时间,可以被一个宽度极小的矩形卡死,看实际情况吧
//By AcerMo
#include
#include
#include
#include
#include
using namespace std;
const int M=100500;
int n,m,e,f[M][20];
inline void read(int &x)
{
x=0;char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return ;
}
inline void write(int x)
{
if (x>9) write(x/10);
putchar(x%10+'0');
return ;
}
inline void pre()
{
for (int i=1;i<=e;i++)
for (int k=1;k<=n-(1<<i)+1;k++)
f[k][i]=max(f[k][i-1],f[k+(1<<(i-1))][i-1]);
return ;
}
inline int que(int l,int r)
{
int t=(log2(r-l+1));
return max(f[l][t],f[r-(1<<t)+1][t]);
}
signed main()
{
read(n);read(m);e=log2(n);
for (int i=1;i<=n;i++)
read(f[i][0]);pre();
for (int i=1;i<=m;i++)
{
int l;read(l);
int r;read(r);
write(que(l,r));puts("");
}
return 0;
}
//By AcerMo
#include
#include
#include
#include
#include
using namespace std;
const int M=305;
int n,m,q;
int f[M][M][10][10];
inline void read(int &x)
{
x=0;char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return ;
}
inline void write(int x)
{
if (x>9) write(x/10);
putchar(x%10+'0');
return ;
}
inline void pre()
{
int t=log2(n),e=log2(m);
for (int i=0;i<=t;i++)
for (int k=0;k<=e;k++)
{
if (i==0&&k==0) continue;
for (int j=1;j<=n-(1<<i)+1;j++)
for (int p=1;p<=m-(1<<k)+1;p++)
if (i==0)
f[j][p][i][k]=max(f[j][p][i][k-1],f[j][p+(1<<(k-1))][i][k-1]);
else
f[j][p][i][k]=max(f[j][p][i-1][k],f[j+(1<<(i-1))][p][i-1][k]);
}
return ;
}
inline int que(int x,int y,int l,int r)
{
int t=log2(l-x+1);
int e=log2(r-y+1);
int a1=max(f[x][y][t][e],f[l-(1<<t)+1][r-(1<<e)+1][t][e]);
int a2=max(f[l-(1<<t)+1][y][t][e],f[x][r-(1<<e)+1][t][e]);
return max(a1,a2);
}
signed main()
{
read(n);read(m);
for (int i=1;i<=n;i++)
for (int k=1;k<=m;k++)
read(f[i][k][0][0]);
pre();read(q);
while (q--)
{
int x,y;read(x),read(y);
int l,r;read(l),read(r);
write(que(x,y,l,r));puts("");
}
return 0;
}