ST表使用说明:
- 常用于离线问题,或当作辅助数据结构,查询时间非常优秀,O(1)
- 二维st表也很好实现,不过本题集中未涉及
- st表内也可以维护的是下标而非值,在某些时候很有用
数列区间最大值
题意简述
输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y,要求你说出 X 到 Y 这段区间内的最大数。
解题思路
st表模板题。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1e5+10;
int st[N][22] ,Log[N];
int a[N];
int getInt(){
int ans = 0;
bool neg = false;
char c = getchar();
while(c!='-' && (c <'0' || c > '9')) c = getchar();
if(c == '-') neg = true,c = getchar();
while(c >= '0' && c <= '9') ans = ans*10+c-'0',c = getchar();
return neg?-ans:ans;
}
void init(){
for(int i = 2;i <= n+1;i++) Log[i]= Log[i/2]+1;
for(int i = 1;i <= n;i++) st[i][0] = a[i];
for(int j = 1;(1<<j) <= n;j++){
for(int i = 1;i + (1<<j-1) <= n;i++){
st[i][j] = max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
}
int ask(int l,int r){
int k = Log[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt();
for(int i = 1;i <= n;i++) a[i] = getInt();
init();
for(int i = 1,x,y;i <= m;i++){
x = getInt(); y = getInt();
printf("%d\n",ask(x,y));
}
return 0;
}
最敏捷的机器人
题意简述
Wind 设计了很多机器人。但是它们都认为自己是最强的,于是,一场比赛开始了……
机器人们都想知道谁是最敏捷的,于是它们进行了如下一个比赛。首先,他们面前会有一排共 n 个数,它们比赛看谁能最先把每连续 k 个数中最大和最小值写下来,当然,这些机器人运算速度都很快,它们比赛的是谁写得快。
但是 Wind 也想知道答案,你能帮助他吗?
解题思路
st表模板题,不过还要维护一下最小值,多开一个数组即可。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1e5+10;
int st[N][22] ,Log[N], st2[N][22];
int a[N];
int getInt(){
int ans = 0;
bool neg = false;
char c = getchar();
while(c!='-' && (c <'0' || c > '9')) c = getchar();
if(c == '-') neg = true,c = getchar();
while(c >= '0' && c <= '9') ans = ans*10+c-'0',c = getchar();
return neg?-ans:ans;
}
void init(){
for(int i = 2;i <= n+1;i++) Log[i]= Log[i/2]+1;
for(int i = 1;i <= n;i++) st2[i][0] = st[i][0] = a[i];
for(int j = 1;(1<<j) <= n;j++){
for(int i = 1;i + (1<<j-1) <= n;i++){
st[i][j] = max(st[i][j-1],st[i+(1<<j-1)][j-1]);
st2[i][j] = min(st2[i][j-1],st2[i+(1<<j-1)][j-1]);
}
}
}
void ask(int l,int r){
int k = Log[r-l+1];
int mx = max(st[l][k],st[r-(1<<k)+1][k]);
int mi = min(st2[l][k],st2[r-(1<<k)+1][k]);
printf("%d %d\n",mx,mi);
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt();
for(int i = 1;i <= n;i++) a[i] = getInt();
init();
for(int i = 1;i <= n-m+1;i++) ask(i,i+m-1);
return 0;
}
与众不同*
题意简述
A 是某公司的 CEO,每个月都会有员工把公司的盈利数据送给 A,A 是个与众不同的怪人,A 不注重盈利还是亏本,而是喜欢研究「完美序列」:一段连续的序列满足序列中的数互不相同。
A 想知道区间 [L,R] 之间最长的完美序列长度。
解题思路
我们可以通过标记数组在O(N)时间内求出每个位置“作为结束位置”时的最长完美序列长度,以及它的起点。
设last[ x ] 表示 x 上次出现的位置;bgn[ p ]表示以位置 p 为末尾的完美序列的起点位置;len[p] 表示以位置 p 为末尾的完美序列的长度。显然我们可以在一次遍历内更新完毕上述三个数组,O(N)。
那么对于任意区间[ L , R ],其内的最长完美序列长度仅有 2 种可能,一种是终点在区间内,起点不在;另一种是起点和终点都在区间内。此时我们已经可以通过一次遍历来求答案了,但是复杂度最差O(N),不可取。由于bgn[]是单调递增的,所以对于某个位置 pos,[ L , pos-1] 所有位置上的起点都小于 L,[pos , R]上所有位置上的起点都大于L,那么我们可以通过ST表维护 [pos ,R] 上的最大的 len 值,在O(1)时间内求出,那么答案就是max(pos-L , ask(pos , R) )。
我们可以通过二分搜索来查找 pos ,复杂度 O(log N),根据len数组构造st表,O(NlogN),总时间复杂度O( (N+M)logN )。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 2e5+10;
const int SZ = 2e6+10;
const int B = 1e6;
/*last[x]为x上一次出现的位置;
bgn[p]为以位置p结尾的起点位置;len[p]为以p结尾的完美序列长度*/
int last[SZ],bgn[N],len[N];
int a[N],Log[N],st[N][22];
void init_st(){
for(int i = 2;i <= n+1;i++) Log[i] = Log[i/2]+1;
for(int i = 1;i <= n;i++) st[i][0] = len[i];
for(int j = 1;1<<j <= n;j++)
for(int i = 1;i + (1<<j-1) <= n;i++)
st[i][j] = max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int ask(int l,int r){
int k = Log[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int bsearch(int l,int r,int x){
while(l <= r){
int mid = l+r>>1;
if(bgn[mid] >= x) r = mid-1;
else l = mid+1;
}
return l;
}
void solve(){
for(int i = 1;i <= n;i++){
bgn[i] = max(bgn[i-1],last[a[i]+B]+1);//起始位置必须合法
len[i] = i - bgn[i] + 1;
last[a[i]+B] = i;
}
init_st();//建立len数组的st表
for(int i = 1,x,y,p,res;i <= m;i++){
scanf("%d%d",&x,&y);x++,y++;
p = bsearch(x,y,x);//找到[x,y]内第一个bgn大于等于x的位置
if(p <= y) res = max(p-x,ask(p,y));
else res = p-x;
printf("%d\n",res);
}
}
int main(){
//freopen("123.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++) scanf("%d",a+i);
solve();
return 0;
}
天才的记忆
代码示例: 模板模板,题面和思路不说了。
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 2e5+10;
int st[N][22] ,Log[N];
int a[N];
int getInt(){
int ans = 0;
bool neg = false;
char c = getchar();
while(c!='-' && (c <'0' || c > '9')) c = getchar();
if(c == '-') neg = true,c = getchar();
while(c >= '0' && c <= '9') ans = ans*10+c-'0',c = getchar();
return neg?-ans:ans;
}
void init(){
for(int i = 2;i <= n+1;i++) Log[i]= Log[i/2]+1;
for(int i = 1;i <= n;i++) st[i][0] = a[i];
for(int j = 1;(1<<j) <= n;j++){
for(int i = 1;i + (1<<j-1) <= n;i++){
st[i][j] = max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
}
}
int ask(int l,int r){
int k = Log[r-l+1];
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt();;
for(int i = 1;i <= n;i++) a[i] = getInt();
init(); m = getInt();
for(int i = 1,x,y;i <= m;i++){
x = getInt(); y = getInt();
printf("%d\n",ask(x,y));
}
return 0;
}
Balanced Lineup
代码示例: 和“敏捷的机器人”一样,都是要维护最大以及最小值。
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1e5+10;
int st[N][22] ,Log[N], st2[N][22];
int a[N];
int getInt(){
int ans = 0;
bool neg = false;
char c = getchar();
while(c!='-' && (c <'0' || c > '9')) c = getchar();
if(c == '-') neg = true,c = getchar();
while(c >= '0' && c <= '9') ans = ans*10+c-'0',c = getchar();
return neg?-ans:ans;
}
void init(){
for(int i = 2;i <= n+1;i++) Log[i]= Log[i/2]+1;
for(int i = 1;i <= n;i++) st2[i][0] = st[i][0] = a[i];
for(int j = 1;(1<<j) <= n;j++){
for(int i = 1;i + (1<<j-1) <= n;i++){
st[i][j] = max(st[i][j-1],st[i+(1<<j-1)][j-1]);
st2[i][j] = min(st2[i][j-1],st2[i+(1<<j-1)][j-1]);
}
}
}
void ask(int l,int r){
int k = Log[r-l+1];
int mx = max(st[l][k],st[r-(1<<k)+1][k]);
int mi = min(st2[l][k],st2[r-(1<<k)+1][k]);
printf("%d\n",mx-mi);
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt();
for(int i = 1;i <= n;i++) a[i] = getInt();
init();
for(int i = 1,x,y;i <= m;i++){
x = getInt(); y = getInt();
ask(x,y);
}
return 0;
}
选择客栈*
题意描述
丽江河边有 n 家很有特色的客栈,客栈按照其位置顺序从 1 到 n 编号。
每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 k−1 表示),且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。
两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定分别住在色调相同的两家客栈中。
晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈),且咖啡店的最低消费不超过 p 。
他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过 p 元的咖啡店小聚。
解题思路
没看正解是啥,我没用到st表,不过倒是用到了求RMQ的步骤。
共三个辅助数组,col[k] 表示当前第k种颜色客栈的数量;val[x]表示客栈x的最低消费;mip[x]表示 [x, n] 内第一个最低消费小于等于 p 元的客栈位置。
如果第一个人入住客栈x,第二个人能够入住的旅店的位置一定是大于等于mip[x]的,并且要颜色和 x 相同,即col[ a[i] ]种选择方案。所以我们只需要从前往后顺序遍历一遍并统计答案即可,复杂度O(N)。
代码示例
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+10;
const int K = 1e4+10;
typedef long long ll;
int col[K];
int n,m,p;
int a[N],val[N];
int getInt(){
int ans = 0;
bool neg = false;
char c = getchar();
while(c!='-' && (c <'0' || c > '9')) c = getchar();
if(c == '-') neg = true,c = getchar();
while(c >= '0' && c <= '9') ans = ans*10+c-'0',c = getchar();
return neg?-ans:ans;
}
int mip[N];
void solve(){
mip[n+1] = n+1;
for(int i = n;i > 0;i--){
if(val[i] > p) mip[i] = mip[i+1];
else mip[i] = i;
}
int pre = 1; ll ans = 0;
for(int i = 1;i <= n;i++){
int now = mip[i];
for(int j = pre;j < now;j++) col[a[j]]--;
//printf("%d %d %d\n",pre,now,col[a[i]]);
ans += col[a[i]]; pre = now;
if(now == i) ans--;
}
printf("%lld\n",ans);
}
int main(){
//freopen("123.txt","r",stdin);
n = getInt(); m = getInt(); p = getInt();
for(int i = 1,x;i <= n;i++){
a[i] = getInt(); val[i] = getInt();
col[a[i]]++;
}
solve();
return 0;
}