一维RMQ问题题集

书本配套OJ
本校OJ

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;
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页