前几天群里看到有人问[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=;
static char buf[BUFSIZE],*begin,*end;
if(begin==end) {
begin=buf;
end=buf+fread(buf,,BUFSIZE,stdin);
if(begin==end) return -;
}
return *begin++;
}
}
inline void read(int &in) {
int c,symbol=;
while(isspace(c=IO::getchar()));
if(c=='-') { in=;symbol=-; }
else in=c-'';
while(isdigit(c=IO::getchar())) { in*=;in+=c-''; }
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+;
const int logn=;
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[]=;
FOR(i,,maxn) Log[i]=Log[i/]+;
scanf("%d%d",&n,&m);
For(i,,n) scanf("%d",&d[i][]);
//For(i,1,10) printf("log[%d]=%d\n",i,Log[i]);
for(int j=;(<<j)<=n;++j)
for(int i=;i+(<<j)-<=n;++i) d[i][j]=std::max(d[i][j-],d[i+(<<(j-))][j-]);
while(m--){
int l,r;scanf("%d%d",&l,&r);
int k=Log[r-l+];
printf("%d\n",std::max(d[l][k],d[r-(<<k)+][k]));
} #ifdef MengLan
printf("Time: %d\n",clock()-Beginning);
system("pause");
#endif // MengLan
return ;
}

[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=;
static char buf[BUFSIZE],*begin,*end;
if(begin==end) {
begin=buf;
end=buf+fread(buf,,BUFSIZE,stdin);
if(begin==end) return -;
}
return *begin++;
}
}
inline void read(int &in) {
int c,symbol=;
while(isspace(c=IO::getchar()));
if(c=='-') { in=;symbol=-; }
else in=c-'';
while(isdigit(c=IO::getchar())) { in*=;in+=c-''; }
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+;
const int logn=;
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,,n) scanf("%d%d",&in[i].y,&in[i].r),d[i][]=in[i].r;
scanf("%d",&m); Log[]=;
For(i,,n) Log[i]=Log[i/]+;
for(int j=;(<<j)<=n;++j)
for(int i=;i+(<<j)-<n;++i) d[i][j]=std::max(d[i][j-],d[i+(<<(j-))][j-]); 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+==x) puts("true");//空集,区间(y,x)直接不存在其他年份,由上一个条件知道y年降雨量比x年多,那么显然是对的
else if(l+==r) puts("maybe");//由两个条件可知,区间(y,x)直接存在其他年份,但是却没有这一年或多年的记录,那么显然maybe
else{//上面的特殊情况终于完了,下面是正常情况
int L=l+,R=r-;
int k=Log[R-L+];
int max=std::max(d[L][k],d[R-(<<k)+][k]);//区间(y,x)的最大值
if(max>=in[r].r) puts("false");
else if(x-y+==r-l+) puts("true");//区间(y,x)内每年都有记录,那么可以准确回答
else puts("maybe");
}
}
else if(in[l].y==y){
if(l+==r) puts("maybe");//区间(y,x)没有记录,而我也不知道x年的值,那么只能maybe
else{//区间(y,x)有记录,那么我假设x年降雨量和y年一样,也符合“X年的降雨量不超过Y年”,那么就看(y,x)是否比y要小了
int L=l+,R=r-;
int k=Log[R-L+];
int max=std::max(d[L][k],d[R-(<<k)+][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-;
int k=Log[R-L+];
int max=std::max(d[L][k],d[R-(<<k)+][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 ;
}

[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=;
static char buf[BUFSIZE],*begin,*end;
if(begin==end) {
begin=buf;
end=buf+fread(buf,,BUFSIZE,stdin);
if(begin==end) return -;
}
return *begin++;
}
}
inline void read(int &in) {
int c,symbol=;
while(isspace(c=IO::getchar()));
if(c=='-') { in=;symbol=-; }
else in=c-'';
while(isdigit(c=IO::getchar())) { in*=;in+=c-''; }
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+;
const int logn=;
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=;
int size=;
while(M--){
char cmd[];
ll in;
scanf("%s%lld",cmd,&in);
if(cmd[]=='A'){
in=(in+t)%D;
d[size][]=in;
//直接更新到最高层去,省得以后多加一层,我还需要跑去处理最上层的东西,更好写,常数会高一点,毕竟nlogn跑满了
FOR(k,,logn){
int len=(<<k);
int L=size-len+;//区间[L,size],长度为2^k
if(L<) d[][k]=std::max(d[][k],in);//2^k>size,d[0][k]表示的区间大于当前我值的个数,那么全部更新到以0开始长度为2^k的区间里面去
else d[L][k]=std::max(d[L][k-],d[L+(<<(k-))][k-]);
}
++size;
}
else{
if(in==) puts("");
else{
int k=;
while((<<(k+))<=in) ++k;
int L=size-in;
t=std::max(d[L][k],d[size-(<<k)][k]);
printf("%lld\n",t);
}
}
} #ifdef MengLan
printf("Time: %d\n",clock()-Beginning);
system("pause");
#endif // MengLan
return ;
}

[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=;
static char buf[BUFSIZE],*begin,*end;
if(begin==end) {
begin=buf;
end=buf+fread(buf,,BUFSIZE,stdin);
if(begin==end) return -;
}
return *begin++;
}
}
inline void read(int &in) {
int c,symbol=;
while(isspace(c=IO::getchar()));
if(c=='-') { in=;symbol=-; }
else in=c-'';
while(isdigit(c=IO::getchar())) { in*=;in+=c-''; }
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+;
const int logn=;
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,,a) FOR(j,,b){
scanf("%d",in[i]+j);
max[i][j][]=min[i][j][]=in[i][j];
} for(int k=;(<<k)<=n;++k) {
for(int i=;i+(<<k)-<a;++i) for(int j=;j+(<<k)-<b;++j){
max[i][j][k]=std::max({max[i][j][k-],max[i][j+(<<(k-))][k-],max[i+(<<(k-))][j][k-],max[i+(<<(k-))][j+(<<(k-))][k-]});//C++11的新特性,函数参数可以传个initializer_list过去,说白了就是传个特定类型数组过去
min[i][j][k]=std::min({min[i][j][k-],min[i][j+(<<(k-))][k-],min[i+(<<(k-))][j][k-],min[i+(<<(k-))][j+(<<(k-))][k-]});
//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=,ans=2e9;
while((<<(k+))<=n) ++k;
for(int i=;i+n-<a;++i) for(int j=;j+n-<b;++j){
int Max=std::max({max[i][j][k],max[i][j+n-(<<k)][k],max[i+n-(<<k)][j][k],max[i+n-(<<k)][j+n-(<<k)][k]});
int Min=std::min({min[i][j][k],min[i][j+n-(<<k)][k],min[i+n-(<<k)][j][k],min[i+n-(<<k)][j+n-(<<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 ;
}

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

Round #4 RMQ问题ST算法的更多相关文章

  1. RMQ的ST算法

    ·RMQ的ST算法    状态设计:        F[i, j]表示从第i个数起连续2^j个数中的最大值    状态转移方程(二进制思想):        F[i, j]=max(F[i,j-1], ...

  2. RMQ(ST算法)

    RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列a,回答若干询问RMQ(A,i,j)(i, j<=n),返回数列a中下标在i ...

  3. RMQ之ST算法模板

    #include<stdio.h> #include<string.h> #include<iostream> using namespace std; ; ],M ...

  4. RMQ问题+ST算法

    一.相关定义 RMQ问题 求给定区间的最值: 一般题目给定许多询问区间. 常见问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大 ...

  5. RMQ问题——ST算法

    比赛当中,常会出现RMQ问题,即求区间最大(小)值.我们该怎样解决呢? 主要方法有线段树.ST.树状数组.splay. 例题 题目描述 2008年9月25日21点10分,酒泉卫星发射中心指控大厅里,随 ...

  6. [总结]RMQ问题&ST算法

    目录 一.ST算法 二.ST算法の具体实现 1. 初始化 2. 求出ST表 3. 询问 三.例题 例1:P3865 [模板]ST表 例2:P2880 [USACO07JAN]平衡的阵容Balanced ...

  7. RMQ问题ST算法 (还需要进一步完善)

    /* RMQ(Range Minimum/Maximum Query)问题: RMQ问题是求给定区间中的最值问题.当然,最简单的算法是O(n)的,但是对于查询次数很多(设置多大100万次),O(n)的 ...

  8. RMQ 问题 ST 算法(模板)

    解决区间查询最大值最小值的问题 用 $O(N * logN)$ 的复杂度预处理 查询的时候只要 $O(1)$ 的时间  这个算法是 real 小清新了   有一个长度为 N 的数组进行 M 次查询 可 ...

  9. RMQ之ST算法

    #include <stdio.h> #include <string.h> ; int a[N]; ]; inline int min(const int &a, c ...

随机推荐

  1. C# - 设计模式 - 虚模式

    虚模式 问题场景 子类从抽象派生是为了解耦,但为了适应新的变化,可能需要向抽象类添加新功能,假如在添加新功能之前已经有100个子类实现了抽象,那么接下来你得在100个子类中去重写向抽象添加的新功能. ...

  2. [经验交流] 影响 kubernetes 稳定性的因素

    使用k8s已有近一年的时间,版本从1.2到1.5.1.6.1.7,期间出现并解决了不少问题,下面是我总结的影响k8s集群稳定性的因素: 1. 安装环境 *kubelet版本最好与kube-apiser ...

  3. 你循环的时候就可以给他们赋值了,那么就不用addClass,再根据类选择器处理,代码能一气呵成就别写成两段了

    function onCopyButtonClick() { $(".index:checked").each(function () { $(] + "__WeekCo ...

  4. css3兼容360

    在头部添加 <!--启用360浏览器的极速模式(webkit) --> <meta name="renderer" content="webkit&qu ...

  5. gitignore规则探究

    PS:转自https://blog.csdn.net/o07sai/article/details/81043474 网上有好多gitignore的帖子,文章,都说很简单的.但是我怎么就用不好呢? 我 ...

  6. js变量传递

    基本类型.引用类型 基本类型: undefined.Null.Boolean.Number.String五种 (简单的数据段);引用类型: object (由多个值构成). 两种类型在使用上的区别: ...

  7. linux 服务配置

    1.基本的linux 服务器防火墙配置 2.配置之前如果需要将之前的所有规则清楚 iptables -F   -------清除预设表filter中的所用规则链的规则 iptables -X ---- ...

  8. HihoCoder - 1038 01背包 动态规划

    #1038 : 01背包 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 且说上一周的故事里,小Hi和小Ho费劲心思终于拿到了茫茫多的奖券!而现在,终于到了小Ho领取奖励 ...

  9. centos7设置httpd

    1.httpd开机自启动 systemctl enable httpd.service 2.httpd开机不自启动 systemctl disable httpd.service 3.启动httpd ...

  10. MySQL 常用命令(Linux)

    一.查看MySQL服务 MySQL服务应该随开机自动启动的.正常情况下,查看开机自动启动的服务使用chkconfig命令,如下: chkconfig --list 实际mysql是否真正启动,是否真正 ...