ST表--倍增区间DP处理RMQ

qq:一切问题没有他线段树解决不了的,但我懒得打那么长就学学这东东...(

多数参考洛谷题解

一.简单介绍

预处理:

①区间DP   转移方程  f[i][j] = max(f[i][j - 1],f[i + 2^{j-1}][j - 1])  f[i][j]表示从i位置开始的后2^j个数中的最大值

这里2^{^{j}}分成两半,一半是2^{j-1}   即   i ~ i+2^{j-1}   和 i+2^{j-1}i+2^{j-1}+2^{j-1}=i+2^{^{j}}

②不过区间在增加时,每次并不是增加一个长度,而是基于倍增思想,用二进制右移,每次增加2^i个长度 ,最多增加logn次

这样预处理了所有2的幂次的小区间的最值

(其实快速幂是倍增法很好的例子)          关于倍增法链接

查询:

③对于每个区间,分成两段长度为2^{k}的区间,再取个最值

(这里的两个区间是可以有交集的,因为重复区间并不影响最值)

比如3,4,6,5,3一种分成3,4,6和6,5,3,另一种分成3,4,6和5,3,最大值都是6,没影响。

④所以O(nlogn)预处理,O(1)查询最值  但不支持修改

二.代码介绍

①预处理外层for上界:求到n最少需要加上2的k次方,即写成k=log2(n)

然后注意先换底公式(经qq提醒,或可直接用log2函数),然后转换成double,最后应取下界(即取int)防溢出

②初始化f[i][0]=a[i]:   即  j=0显然区间[i,i]里只有它一个数

③预处理两层for:外层j内层i,把j放外面,因为上界由i和j共同决定,而且区间长度由j决定,保证dp从小区间合并为大区间

注意内层循环,区间右端点下标为i+2^j-1,-1别漏

④预处理   f[i][j] = max(f[i][j - 1],f[i + 2^(j - 1)][j - 1])  或者·min

查询划分点

左区间右端点2^p:求l到r最少需要加上2的p次方,即写成p=log2(r-l+1),后面同①

右区间左端点k  :证明:  [k][k+2^{p}-1]<=>[k][r]     k=r-2^p+1   因为p与区间长度有关,有时用len数组预处理某区间长度的k值

注意查询的l和r之间区间长度不一定刚好是2的次方,比如下图l,r,让区间重叠是没问题的

⑥取两个区间最值的最值

三.模板  P3865 甚至见到用回滚莫队笛卡尔树的大佬

①P3865  部分参考ttoj

#include <bits/stdc++.h>
using namespace std;
#define N 500010   
int maxl[N][16], minl[N][16]; 
int  a[N];
int n,m;
void S_table(){
    int l = int(log((double)n)/log(2.0));
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=n;++i){
            maxl[i][j] = max(maxl[i][j-1], maxl[i + (1 << (j-1) )][j-1]);
            minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
        }
    }
}
int rmq(int l, int r){
    int k = int(log((double)(r-l+1))/log(2.0));
    int a1 = max(maxl[l][k], maxl[r - (1<<k) + 1][k]);
    int a2 = min(minl[l][k], minl[r - (1<<k) + 1][k]);
    return a1;
    //printf("Max: %d Min: %d\n", a1, a2);
}


int main()
{
	int x,y;
	cin>>n>>m; 
   for (int i=1;i<=n;++i)
    {
    scanf("%d", &a[i]);
    maxl[i][0] = minl[i][0] = a[i];
    }
    S_table();
    while(m--)
    {
    	scanf("%d %d",&x,&y);
    	printf("%d\n",rmq(x, y));
	}
   
}

②poj 3264 裸题,最大-最小

#include <iostream>
#include<algorithm>
#include<stdio.h>
#include<cmath>
using namespace std;
#define N 500010   
int maxl[N][16], minl[N][16]; 
int  a[N];
int n,m;
void S_table(){
    int l = int(log((double)n)/log(2.0));
    for (int j=1;j<=l;j++){
        for (int i=1; i + (1 << (j-1) ) - 1 <=n;++i){
            maxl[i][j] = max(maxl[i][j-1], maxl[i + (1 << (j-1) )][j-1]);
            minl[i][j] = min(minl[i][j-1], minl[i + (1 << (j-1) )][j-1]);
        }
    }
}
int rmq(int l, int r){
    int k = int(log((double)(r-l+1))/log(2.0));
    int a1 = max(maxl[l][k], maxl[r - (1<<k) + 1][k]);
    int a2 = min(minl[l][k], minl[r - (1<<k) + 1][k]);
    return a1-a2;
    //printf("Max: %d Min: %d\n", a1, a2);
}


int main()
{
	int x,y;
	cin>>n>>m; 
   for (int i=1;i<=n;++i)
    {
    scanf("%d", &a[i]);
    maxl[i][0] = minl[i][0] = a[i];
    }
    S_table();
    while(m--)
    {
    	scanf("%d %d",&x,&y);
    	printf("%d\n",rmq(x, y));
	}
   
}

③华工校赛

区间gcd题解代码(gcd类似最值的性质,见链接

#include<bits/stdc++.h>

using namespace std;

#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=a; i<=b; ++i)
#define PER(i,a,b) for(int i=a; i>=b; --i)
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long LL;
typedef double DB;

const int maxn = 5e5;
const int Step = 17;
const int P = 1e9+7;

LL Gcd(LL a, LL b) { return b ? Gcd(b,a%b) : a; }

int n, ai[maxn+5];
int st[maxn+5][Step+2];

int len[maxn+5];

int Que(int l, int r) {
    int k = len[r-l+1];
    return Gcd( st[l][k], st[r-(1<<k)+1][k] );
}

int main() {
    scanf("%d", &n);
    REP(i,1,n) scanf("%d", ai+i), st[i][0] = ai[i];
    for(int k = 1; (1<<k) <= n; ++k) {
        for(int i = 1; i+(1<<k)-1 <= n; ++i) {
            st[i][k] = Gcd(st[i][k-1], st[i+(1<<(k-1))][k-1]);
        }
    }
    for(int i = 1; i <= n; ++i) {
        len[i] = len[i-1];
        if((1<<(len[i]+1)) < i) ++len[i];
    }
    LL ans = 0;
    REP(i,1,n) {
        int now = 0;
        for(int k = i, nx; k <= n; k = nx+1) {
            now = Gcd(now, ai[k]);
            int l = k+1, r = n;
            nx = k;
            while(l<=r) {
                int mid = (l+r)>>1;
                int tmp = Que(k,mid);
                if(tmp%now==0) nx = mid, l = mid+1;
                else r = mid-1;
            }
            ans = (ans + LL(nx-k+1) * now) % P;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zjyang12345/article/details/89363091
今日推荐