前几天群里看到有人问[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表,唯一可以说的就是,题目说明了时间卡的紧,务必保证查询复杂度为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 ;
}
很后悔加了这道题,因为这道题没什么难的,就是要讨论的东西有点多而已。。。
题意有两个条件,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 ;
}
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 ;
}
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,怎么花了好像两个多小时?睡觉睡觉