一維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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章