ST算法

来源:互联网 发布:手机淘宝怎样交电费 编辑:程序博客网 时间:2024/04/28 13:08

对于一个长度为n的序列,有m个询问,每次询问输入一个区间,输出这个区间的最大值和最小值。

看到题目的第一感觉就是线段树。今天学习了一种新的算法,ST算法。

f[i][j]表示的是[i,i+2^j-1]这样一个区间,比如说对于1,2,3,4这样一个序列f[1][0]表示的就是1,f[1][1]表示的就是1,2.即以第i个数为起点查2^j个数。

对于求f[i][j]的最大值,我们可以将这个区间分成两个部分,分别是f[i][j-1],f[i+1<<(j-1)][j-1],f[i][j]=Max(f[i][j-1],f[i+<<(j-1)][j-1])。

对于f[i][j]的初始化就不讲了,参照代码很容易理解。

现在,在我们初始化之后,f[i][j]记录的是[i,i+2^j-1]这个区间内的最大值或者最小值。

对于我们想要查询的区间[x,y],我们可以按照这个区间的长度(y-x+1),将区间分为两个部分构成,一部分是以x为起点,长度为2^k(k=(int)log2(y-x+1))的区间,一部分是以y为终点,长度为2^k的区间。在我们初始化之后,这两个区间的值都是知道的,即f[x][k]和f[y-2^k+1][k],这两个区间有交集,保证了数据不会遗漏。这两个值都是已知的,我们只需要取两者的较大值或者较小值,即为我们想要得到的结果。这样就将查询的时间复杂度降到了1。

关于ST算法可以参考这篇博客:点击打开链接

还有百度百科:点击打开链接

代码:

#include<stdio.h>#include<cmath>#include<string.h>#define N 500005int max[N][20],min[N][20],a[N];int n,m;int Max(int x,int y){    if(x>y) return x;    return y;}int Min(int x,int y){    if(x<y) return x;    return y;}void Init()//初始化{    for(int i=1;i<=n;i++)        max[i][0]=min[i][0]=a[i];    int k=(int)log2(n);    for(int i=1;i<=k;i++)    {        for(int j=n;j>=1;j--)        {            max[j][i]=max[j][i-1];//继承了[j,1<<i]这个区间中前半部分的值,即[j,1<<(i-1)]的最大值。            if(j+(1<<(i-1))<=n)//如果[j,1<<(i-1)]没有覆盖整个序列,还要拿前半部分的值和后半部分的值比较                max[j][i]=Max(max[j][i],max[j+(1<<(i-1))][i-1]);            min[j][i]=min[j][i-1];            if(j+(1<<(i-1))<=n)                min[j][i]=Min(min[j][i],min[j+(1<<(i-1))][i-1]);        }    }    return ;}int askmax(int x,int y){    int k=(int)log2(y-x+1);//2^k可以小于y-x+1,但是2^(k+1)一定大于等于y-x+1,这是为了保证区间完全覆盖,没有遗漏。    return Max(max[x][k],max[y-(1<<k)+1][k]);//将需要查询的区间分割成两个区间,并且两个区间之间是有交集的,这就保证了不会遗漏值}int askmin(int x,int y){    int k=(int)log2(y-x+1);    return Min(min[x][k],min[y-(1<<k)+1][k]);}int main(){    while(scanf("%d%d",&n,&m)!=EOF)    {        for(int i=1;i<=n;i++)            scanf("%d",&a[i]);        Init();        while(m--)        {            int x,y;            scanf("%d%d",&x,&y);            printf("%d %d\n",askmax(x,y),askmin(x,y));        }    }    return 0;}