ST算法能够解决什么问题?
时间复杂度O(nlogn)预处理、O(1)查询区间最大值、最小值等。
ST算法讲解:
该算法采用二进制倍增的思想,二进制倍增就是1,2,4,8,16...
比如现在有8个数,求min:
2 3 4 5 6 4 3 2
现在你要查询的区间是[l,r]
如果l==r就直接可以查询该位置数即可,但是查询[1,4],[3,8]这类呢?
假如问题换成q<=10^5次查询,每次查询肯定要时间复杂度O(1),才不会超时,
O(1)情况下,肯定是要打表了!!
然后打个dp[i][j]表~
下标:0 1 2 3 j <---倍增在这里!!
------------------
1| 2 2 2 2
2| 3 3 3 0
3| 4 4 4 0
4| 5 5 3 0
5| 6 4 2 0
6| 4 3 0 0
7| 3 2 0 0
8| 2 0 0 0
i|
0: 会发现dp[i][0]这一列是原数组
1: dp[i][1]这列怎么来的?
dp[i][1]=min(dp[i][j-1],dp[i+1][j-1])
比如:dp[4][1]=4 --> min(dp[4][0],dp[5][0])
2: dp[i][2]=min(dp[i][j-1],dp[i+2][j-1])
比如:dp[4][2]=3 --> min(dp[4][1],dp[6][1])
想想转移方程为什么这样写?
/ dp[4][0]
先取上一列dp[4][1]
/ \ dp[5][0]
当前点为dp[4][2]
\ / dp[6][0]
再去上一列dp[6][1]
\ dp[7][0]
发现了什么?
dp[4][1]所代表的为下标(4,5)的最小值。
dp[4][2]所代表的为下标(4,5,6,7)的最小值。
同理dp[1][3]所代表的为下标(1,2,3,4,5,6,7,8)的最小值。
j=0时,区间为本身。
j=1时,区间为相邻2个数。
j=2时,区间为相邻4个数。
j=3时,区间为相邻8个数。
.....
##** 这是不是倍增? **##
普及知识点:
log以2为底的8=3 -->log(8)/log(2)=3 (log的换底公式)
1.查询区间[2,3]
区间共两个数,k=(int)((log(r-l+1.0)/log(2.0)))=1;
查询min(dp[2][k],dp[2][k]);
2.查询区间[2,4]
区间共三个数,k=1;
查询min(dp[2][1],dp[3][1]);
(dp[2][1]是区间(2,3)的最小值,dp[3][1]是区间(3,4)的最小值,所以合并查询的是区间[2,3,4])
3.查询区间[2,5]
区间共四个数,k=2;
查询min(dp[2][2],dp[2][2]);
(dp[2][2]是区间(2,3,4,5)的最小值,合并区间[2,3,4,5])
4.查询区间[2,7]
区间共六个数,k=2;
查询min(dp[2][2],dp[4][2]);
(dp[2][2]区间(2,3,4,5),dp[3][2]区间(4,5,6,7),合并区间(2,3,4,5,6,7))
5.查询区间[1,8]
区间共八个数,k=3;
查询min(dp[1][3],dp[1][3]);
.....合并区间(1,2,3,4,5,6,7,8)
#include <iostream>
#include <cmath>
using namespace std;
int n;
int a[100005];
int dp[100005][17];
void ST()
{
//n为数组个数
//bitn为n的二进制位数
int bitn=log(n)/log(2)+1;
for(int i=1; i<=n;i++)
dp[i][0]=a[i];
for(int j=1;j<bitn;j++)
for(int i=1;i<=n;i++)
{
if(i+(1<<(j-1))>n) break;
dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
int query(int l,int r) //查询区间[s,e]的最值
{
int k=(int)((log(r-l+1.0)/log(2.0)));
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
/*
2 3 4 5 6 4 3 2
2 2 2 2
3 3 3 0
4 4 4 0
5 5 3 0
6 4 2 0
4 3 0 0
3 2 0 0
2 0 0 0
*/