Round #4 RMQ问题ST算法

前几天群里看到有人问[JSOI2008]最大数,一道很简单的问题,线段树无脑做,但是看到了动态ST,emmm,学学吧,听大佬说了下思路,还好,不难的;

四道题都可以用其他数据结构或做法代替,例如线段树,dp什么的,但这不重要,毕竟学的就是ST表,触类旁通,数据结构很多知识都是可以互通的,例如一维推广到二维,可持久化这些;

倍增思想,常见的有:

1. 2^(x1)+2^(x2)...2^(xn)=2^n (max{xi}<=logn)

对于正整数x,存在一个二进制表示方法,例如11=1011(2),那么也就是11可以用2^3+2^1+2^0表示;

所以快速幂采用这种方法,在log(n)的时间内求出2^n;

2. 2^n=2*2^(n-1)=2^(n-1)+2^(n-1)

假设我们知道了间隔为2^x的最小值,那么我们可以把两个相邻的间隔为2^x的最小值合并成间隔为2^(x+1)最小值;

那么我们查询区间[L,R]的最小值,我们可以通过查询区间长度为2^k(k=floor(log(R-L+1)))的两个区间[L,L+2^k-1],[R-2^k+1,R]的最小值得到答案,而得到区间以i为起点,长度为2^k的区间的最小值,就可以通过前面说的合并方式预处理得到,那么这就是ST表了;

 

【模板】ST表

就...一道裸的ST表,唯一可以说的就是,题目说明了时间卡的紧,务必保证查询复杂度为O(1),一般来说我们可能会对于查询n的logn去枚举一下,那么这题都这样说了,那我就O(1)吧,不枚举了;

我来递推logn的值,边界Log(1)=0,然后就Log(n)=Log(n/2)+1;

注意,这里是下取整,即2^k<=n,k的最大值

#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int,int> P;
#define FOR(i,init,len) for(int i=(init);i<(len);++i)
#define For(i,init,len) for(int i=(init);i<=(len);++i)
#define fi first
#define se second
#define pb push_back
#define is insert
namespace IO {
    inline char getchar() {
        static const int BUFSIZE=5201314;
        static char buf[BUFSIZE],*begin,*end;
        if(begin==end) {
            begin=buf;
            end=buf+fread(buf,1,BUFSIZE,stdin);
            if(begin==end) return -1;
        }
        return *begin++;
    }
}
inline void read(int &in) {
    int c,symbol=1;
    while(isspace(c=IO::getchar()));
    if(c=='-') { in=0;symbol=-1; }
    else in=c-'0';
    while(isdigit(c=IO::getchar())) { in*=10;in+=c-'0'; }
    in*=symbol;
}
inline int read() { static int x;read(x);return x; }
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; }
ll lcm(ll a,ll b) { return a/gcd(a,b)*b; }
#define PA(name,init,len) cout<<#name"["<<(len-init)<<"]=";FOR(_,init,len) cout<<name[_]<<" \n"[_==(len-1)];
#define Pa(name,init,len) cout<<#name"["<<(len-init+1)<<"]=";For(_,init,len) cout<<name[_]<<" \n"[_==(len)];
#define PV(name) cout<<#name"="<<name<<'\n';

const int maxn=1e5+10;
const int logn=18;
int n,m;
int d[maxn][logn];
int Log[maxn];

int main() {
#ifdef MengLan
    int Beginning=clock();
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif // MengLan

    Log[1]=0;
    FOR(i,2,maxn) Log[i]=Log[i/2]+1;
    scanf("%d%d",&n,&m);
    For(i,1,n) scanf("%d",&d[i][0]);
    //For(i,1,10) printf("log[%d]=%d\n",i,Log[i]);
    for(int j=1;(1<<j)<=n;++j)
        for(int i=1;i+(1<<j)-1<=n;++i) d[i][j]=std::max(d[i][j-1],d[i+(1<<(j-1))][j-1]);
    while(m--){
        int l,r;scanf("%d%d",&l,&r);
        int k=Log[r-l+1];
        printf("%d\n",std::max(d[l][k],d[r-(1<<k)+1][k]));
    }

#ifdef MengLan
    printf("Time: %d\n",clock()-Beginning);
    system("pause");
#endif // MengLan
    return 0;
}
View Code

[SCOI2007]降雨量

很后悔加了这道题,因为这道题没什么难的,就是要讨论的东西有点多而已。。。

题意有两个条件,1.要求Y年降雨量大于等于X年(重点,一开始没注意这个条件),2.降雨量max{(Y,X)}(注意,(x,y)意思是开区间)的最大值严格小于X年降雨量

分四种情况考虑,每种情况的具体细节见代码注释:

1.Y、X年的降雨量均存在记录,那么按题意做

2.Y年降雨量不存在记录,但X年降雨量存在记录,那么第一个条件无法判断,我们就假设成立吧,则第二个条件成立的话,是maybe,否则就是false

3.Y年存在记录,X年不存在记录,依旧第一个条件无法判断,继续假设成立,那X年的降雨量最做和Y年降雨量一样,那么就取它吧,判断是否符合第二个条件,可以就maybe,否则false

4,X和Y年都不存在,只能maybe了

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<utility>
#include<numeric>
#include<iterator>
#include<algorithm>
#include<functional>
#include<ctime>
#include<cassert>
using std::cin;
using std::cout;
using std::endl;
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int,int> P;
#define FOR(i,init,len) for(int i=(init);i<(len);++i)
#define For(i,init,len) for(int i=(init);i<=(len);++i)
#define fi first
#define se second
#define pb push_back
#define is insert
namespace IO {
    inline char getchar() {
        static const int BUFSIZE=5201314;
        static char buf[BUFSIZE],*begin,*end;
        if(begin==end) {
            begin=buf;
            end=buf+fread(buf,1,BUFSIZE,stdin);
            if(begin==end) return -1;
        }
        return *begin++;
    }
}
inline void read(int &in) {
    int c,symbol=1;
    while(isspace(c=IO::getchar()));
    if(c=='-') { in=0;symbol=-1; }
    else in=c-'0';
    while(isdigit(c=IO::getchar())) { in*=10;in+=c-'0'; }
    in*=symbol;
}
inline int read() { static int x;read(x);return x; }
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; }
ll lcm(ll a,ll b) { return a/gcd(a,b)*b; }
#define PA(name,init,len) cout<<#name"["<<(len-init)<<"]=";FOR(_,init,len) cout<<name[_]<<" \n"[_==(len-1)];
#define Pa(name,init,len) cout<<#name"["<<(len-init+1)<<"]=";For(_,init,len) cout<<name[_]<<" \n"[_==(len)];
#define PV(name) cout<<#name"="<<name<<'\n';

const int maxn=1e5+10;
const int logn=18;
int n,m;
struct Lan{
    int y,r;
    bool operator<(const int &rhs)const{return y<rhs;}
}in[maxn];
int d[maxn][logn],Log[maxn];

int find(int x){return std::lower_bound(in,in+n,x)-in;}

int main() {
#ifdef MengLan
    int Beginning=clock();
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif // MengLan

    scanf("%d",&n);
    FOR(i,0,n) scanf("%d%d",&in[i].y,&in[i].r),d[i][0]=in[i].r;
    scanf("%d",&m);

    Log[1]=0;
    For(i,2,n) Log[i]=Log[i/2]+1;
    for(int j=1;(1<<j)<=n;++j)
        for(int i=0;i+(1<<j)-1<n;++i) d[i][j]=std::max(d[i][j-1],d[i+(1<<(j-1))][j-1]);

    while(m--){
        int x,y;scanf("%d%d",&y,&x);
        //assert(y+1<x);
        int l=find(y),r=find(x);
        if(in[l].y==y&&in[r].y==x){
            if(in[l].r<in[r].r) puts("false");//x年的降雨量比y年大,不符合“X年的降雨量不超过Y年”
            else if(y+1==x) puts("true");//空集,区间(y,x)直接不存在其他年份,由上一个条件知道y年降雨量比x年多,那么显然是对的
            else if(l+1==r) puts("maybe");//由两个条件可知,区间(y,x)直接存在其他年份,但是却没有这一年或多年的记录,那么显然maybe
            else{//上面的特殊情况终于完了,下面是正常情况
                int L=l+1,R=r-1;
                int k=Log[R-L+1];
                int max=std::max(d[L][k],d[R-(1<<k)+1][k]);//区间(y,x)的最大值
                if(max>=in[r].r) puts("false");
                else if(x-y+1==r-l+1) puts("true");//区间(y,x)内每年都有记录,那么可以准确回答
                else puts("maybe");
            }
        }
        else if(in[l].y==y){
            if(l+1==r) puts("maybe");//区间(y,x)没有记录,而我也不知道x年的值,那么只能maybe
            else{//区间(y,x)有记录,那么我假设x年降雨量和y年一样,也符合“X年的降雨量不超过Y年”,那么就看(y,x)是否比y要小了
                int L=l+1,R=r-1;
                int k=Log[R-L+1];
                int max=std::max(d[L][k],d[R-(1<<k)+1][k]);
                if(max>=in[l].r) puts("false");
                else puts("maybe");
            }
        }
        else if(in[r].y==x){
            if(l==r) puts("maybe");//y年没记录,我还找不到(y,x)的记录,那么maybe
            else{//区间(y,x)有记录,假设y年比我x年大,那么我判断(y,x)是不是比x年小就好了
                int L=l,R=r-1;
                int k=Log[R-L+1];
                int max=std::max(d[L][k],d[R-(1<<k)+1][k]);
                if(max>=in[r].r) puts("false");
                else puts("maybe");
            }
        }
        else puts("maybe");
    }

#ifdef MengLan
    printf("Time: %d\n",clock()-Beginning);
    system("pause");
#endif // MengLan
    return 0;
}
View Code

[JSOI2008]最大数

ST表,动态ST表,模拟预处理ST表从0层到logn层的过程,每插入一个元素,从0层开始往上更新,定位左边界L,d[L][k]=min(d[L][k-1],d[L+(1<<(k-1))][k-1])即可

emmm,你会发现每次插入n的时候n还要加上上次询问的t作为最终插入值,意思很明显,强制在线,不然的话,可以考虑离线做法,读取插入数据,得到全部元素,对于询问记录询问时的最右边端点R,然后R-L+1得到左端点,即询问[R-L+1,R]的最大值,最后预处理了ST表,再去对这些询问处理,得到结果输出,说说而已,这题又不能离线,强制在线,而且线段树什么的,做起来更容易

#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int,int> P;
#define FOR(i,init,len) for(int i=(init);i<(len);++i)
#define For(i,init,len) for(int i=(init);i<=(len);++i)
#define fi first
#define se second
#define pb push_back
#define is insert
namespace IO {
    inline char getchar() {
        static const int BUFSIZE=5201314;
        static char buf[BUFSIZE],*begin,*end;
        if(begin==end) {
            begin=buf;
            end=buf+fread(buf,1,BUFSIZE,stdin);
            if(begin==end) return -1;
        }
        return *begin++;
    }
}
inline void read(int &in) {
    int c,symbol=1;
    while(isspace(c=IO::getchar()));
    if(c=='-') { in=0;symbol=-1; }
    else in=c-'0';
    while(isdigit(c=IO::getchar())) { in*=10;in+=c-'0'; }
    in*=symbol;
}
inline int read() { static int x;read(x);return x; }
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; }
ll lcm(ll a,ll b) { return a/gcd(a,b)*b; }
#define PA(name,init,len) cout<<#name"["<<(len-init)<<"]=";FOR(_,init,len) cout<<name[_]<<" \n"[_==(len-1)];
#define Pa(name,init,len) cout<<#name"["<<(len-init+1)<<"]=";For(_,init,len) cout<<name[_]<<" \n"[_==(len)];
#define PV(name) cout<<#name"="<<name<<'\n';

const int maxn=2e5+10;
const int logn=22;
ll d[maxn][logn];
int M,D;

int main() {
#ifdef MengLan
    int Beginning=clock();
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif // MengLan

    scanf("%d%d",&M,&D);
    ll t=0;
    int size=0;
    while(M--){
        char cmd[10];
        ll in;
        scanf("%s%lld",cmd,&in);
        if(cmd[0]=='A'){
            in=(in+t)%D;
            d[size][0]=in;
            //直接更新到最高层去,省得以后多加一层,我还需要跑去处理最上层的东西,更好写,常数会高一点,毕竟nlogn跑满了
            FOR(k,1,logn){
                int len=(1<<k);
                int L=size-len+1;//区间[L,size],长度为2^k
                if(L<0) d[0][k]=std::max(d[0][k],in);//2^k>size,d[0][k]表示的区间大于当前我值的个数,那么全部更新到以0开始长度为2^k的区间里面去
                else d[L][k]=std::max(d[L][k-1],d[L+(1<<(k-1))][k-1]);
            }
            ++size;
        }
        else{
            if(in==0) puts("0");
            else{
                int k=0;
                while((1<<(k+1))<=in) ++k;
                int L=size-in;
                t=std::max(d[L][k],d[size-(1<<k)][k]);
                printf("%lld\n",t);
            }
        }
    }

#ifdef MengLan
    printf("Time: %d\n",clock()-Beginning);
    system("pause");
#endif // MengLan
    return 0;
}
View Code

[HAOI2007]理想的正方形

ST表嘛,那我们就ST表做咯,将一维推广到二维,下午想了一下,然后就去睡觉了,睡醒再一看,简直太简单了,然后开心的算一下空间大小,好像哪里不对,n^2log^2(n),内存有点炸,而且时间和空间复杂度都是n^2log^2(n)的。

二维ST表大概是这样的(开始瞎吹,我也没写过吖)

 

在每次枚举一个y轴上的间隔为2^l的时候,我们在x轴上做一遍一维的预处理,那么对于矩形查询[n,m]的最大/小值,我们都可以找到一个最大的l和k(2^l<=n&&2^k<=m)直接在ST表上查找;

 

这题是求正方形,而且一开始就给定了正方形的边长n,那么我们就可以简化一下下,变成一个查找正方形的ST表,而不是查找矩形的ST表;

和上面有什么区别?区别就是间距只有一个k,表示宽度为2^k的正方形的最小值

那么d[i][j][k]就可以从四个点{d[i][j][k-1],d[i][j+(1<<(k-1))][k-1],d[i+(1<<(k-1))][j][k-1],d[i+(1<<(k-1))][j+(1<<(k-1))][k-1]}转移过来

而一维只需要从左右两个点,这里需要从左上,右上,左下,右下四个点;

#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int,int> P;
#define FOR(i,init,len) for(int i=(init);i<(len);++i)
#define For(i,init,len) for(int i=(init);i<=(len);++i)
#define fi first
#define se second
#define pb push_back
#define is insert
namespace IO {
    inline char getchar() {
        static const int BUFSIZE=5201314;
        static char buf[BUFSIZE],*begin,*end;
        if(begin==end) {
            begin=buf;
            end=buf+fread(buf,1,BUFSIZE,stdin);
            if(begin==end) return -1;
        }
        return *begin++;
    }
}
inline void read(int &in) {
    int c,symbol=1;
    while(isspace(c=IO::getchar()));
    if(c=='-') { in=0;symbol=-1; }
    else in=c-'0';
    while(isdigit(c=IO::getchar())) { in*=10;in+=c-'0'; }
    in*=symbol;
}
inline int read() { static int x;read(x);return x; }
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; }
ll lcm(ll a,ll b) { return a/gcd(a,b)*b; }
#define PA(name,init,len) cout<<#name"["<<(len-init)<<"]=";FOR(_,init,len) cout<<name[_]<<" \n"[_==(len-1)];
#define Pa(name,init,len) cout<<#name"["<<(len-init+1)<<"]=";For(_,init,len) cout<<name[_]<<" \n"[_==(len)];
#define PV(name) cout<<#name"="<<name<<'\n';

const int maxn=1e3+10;
const int logn=13;
int a,b,n;
int in[maxn][maxn];
int max[maxn][maxn][logn],min[maxn][maxn][logn];

int main() {
#ifdef MengLan
    int Beginning=clock();
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif // MengLan

    scanf("%d%d%d",&a,&b,&n);
    FOR(i,0,a) FOR(j,0,b){
        scanf("%d",in[i]+j);
        max[i][j][0]=min[i][j][0]=in[i][j];
    }

    for(int k=1;(1<<k)<=n;++k) {
        for(int i=0;i+(1<<k)-1<a;++i) for(int j=0;j+(1<<k)-1<b;++j){
            max[i][j][k]=std::max({max[i][j][k-1],max[i][j+(1<<(k-1))][k-1],max[i+(1<<(k-1))][j][k-1],max[i+(1<<(k-1))][j+(1<<(k-1))][k-1]});//C++11的新特性,函数参数可以传个initializer_list过去,说白了就是传个特定类型数组过去
            min[i][j][k]=std::min({min[i][j][k-1],min[i][j+(1<<(k-1))][k-1],min[i+(1<<(k-1))][j][k-1],min[i+(1<<(k-1))][j+(1<<(k-1))][k-1]});
            //printf("max[%d][%d][%d]=%d min[%d][%d][%d]=%d\n",i,j,k,max[i][j][k],i,j,k,min[i][j][k]);
        }
    }
    
    //枚举左上角i,j,求区间[i,i+n][j,j+n]的最大最小值作差更新ans
    int k=0,ans=2e9;
    while((1<<(k+1))<=n) ++k;
    for(int i=0;i+n-1<a;++i) for(int j=0;j+n-1<b;++j){
        int Max=std::max({max[i][j][k],max[i][j+n-(1<<k)][k],max[i+n-(1<<k)][j][k],max[i+n-(1<<k)][j+n-(1<<k)][k]});
        int Min=std::min({min[i][j][k],min[i][j+n-(1<<k)][k],min[i+n-(1<<k)][j][k],min[i+n-(1<<k)][j+n-(1<<k)][k]});
        ans=std::min(ans,Max-Min);
    }
    printf("%d\n",ans);

#ifdef MengLan
    printf("Time: %d\n",clock()-Beginning);
    system("pause");
#endif // MengLan
    return 0;
}
View Code

 

写题解什么的,是真的花时间。。。原本以为一小时写得完,然后emmm,怎么花了好像两个多小时?睡觉睡觉

posted @ 2018-05-14 03:05  漫无目的的寻  阅读(131)  评论(0编辑  收藏  举报