APTX Blog

A Moe Blog Set Up By APTX

HDU 1806 Frequent values(区间RMQ问题)题解

洛谷:https://www.luogu.org/problemnew/show/U50167

Description

给定一个非降序的由 n 个整数构成的序列以及 q 个由整数i j 构成的询问。对于每一个询问,输出在 a[i],a[i+1], … ,a[j-1],a[j]里面出现次数最多的那个()整数出现的次数。

Input

第一行包含两个整数, n q。第二行包括 n 个整数 a[1],a[2], … ,a[n],由空格分开。以下 q 行每一行包括一个询问,一个询问由两个整数 i j 构成, i j 指示询问索引的边界。 保证 i<=j

Output

对于每一组询问,输出一行一个整数:在给定区间内出现次数最多的那个()整数出现的次数。

Sample Input

10 3
-1 -1 1 1 1 1 3 10 10 10
2 3
1 10
5 10

Sample Output

1 4 3

解析及代码

暴力:

我们考虑暴力解法,根据题意模拟即可,复杂度\(O(qn^2)\)(不计算Map复杂度)

void baoli() {
	for(int i = 1;i <= m;++i) {
		int l,r,ans = 0;
		map <int ,int > M;
		scanf("%d%d",&l,&r);
		for(int j = l;j <= r;++j) M[a[j]]++,ans = max(ans,M[a[j]]);
		printf("%d\n",ans);
	}
}

正解①:

考虑用ST表维护区间内每个数出现的次数。对于区间l,r,我们可以分为l ->a[l]最后出现的地方 和a[r]一开始出现的地方->r 以及 a[l]最后出现的地方+1到a[r]最开始出现的地方-1三个区间。

对于第一个和第二个区间。我们暴力查找,通过二分将我们需要的a[r]一开始出现的地方 及 a[l]最后出现的地方。倘若l仍然小于r我们在查找满区间(没有被l,r分割的区间),这样我们就得到了\(O(qlogn)\)的算法。可以通过本题。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
using namespace std; 

const int MAXN = 100005;

int a[MAXN];
int lg[MAXN];
int Max[MAXN][22];
int n,Num = 1,m; 

void Init() {
	lg[0] = -1;
	for(int i = 1;i <= n;++i) lg[i] = lg[i >> 1] + 1;
	for(int j = 1;j <= 21;++j)	
		for(int i = 1;i + (1 << j) - 1 <= n;++i)	
			Max[i][j] = max(Max[i][j - 1],Max[i + (1 << (j - 1))][j - 1]);
}

int Query(int l,int r) {
	int k = lg[r - l + 1];
	return max(Max[l][k],Max[r - (1 << k) + 1][k]);
}

int main() {
	freopen("query.in","r",stdin);
	freopen("query.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i) scanf("%d",a + i);
	for(int i = 1;i <= n;++i) {
		if(a[i] == a[i + 1]) Num++;
		else Num = 1;
		Max[i][0] = Num; //到达每一个点时每一个数的最大出现次数 
	}
	Init();
	for(int i = 1;i <= m;++i) {
		int l,r;
		scanf("%d%d",&l,&r); //分段 分l->a[l]最后出现的位置 a[r]一开始出现的位置->r  和  完整的区间进行ST查询 
		int pos2 = lower_bound(a + 1,a + 1 + n,a[r]) - a; // 返回当前a[r]一开始出现的位置
		if(a[pos2] != a[r]) pos2++; 
		if(pos2 < l) pos2 = l; //特判一下,如果a[r]一开始出现的位置比l小,我们是不可取pos2的 
		int Ans = r - pos2 + 1; //一开始定义Ans为a[r]出现的次数 
		r = pos2 - 1; //找下一个完整区间的数 
		if(l > r) { //如果l比r大了,那么我们找完了所有区间 
			printf("%d\n",Ans);
			continue;
		}
		int pos1 = upper_bound(a + 1,a + 1 + n,a[l]) - a; //返回当前a[l]最后出现的位置
		if(a[pos1] != a[l]) pos1--; 
		Ans = max(Ans,pos1 - l + 1); //将两个单独的区间取max 
		//cout << pos1 << " " << pos2 << endl << endl;
		l = pos1 + 1;
		if(l > r) { //如果l比r还大,那么我们已经找完了所有区间 
			printf("%d\n",Ans);
			continue;
		}
		Ans = max(Ans,Query(l,r));  //否则我们寻找完整的区间进行ST 
		printf("%d\n",Ans);
	}
	return 0;
}

正解②:

在维护到达每一个点时每一个数的最大出现次数 时,我们可以倒序枚举,这样就不用管开头的区间了,因为保证了单调递增,这样我们RMQ查询时默认肯定会到达a[l]的最大值的。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
using namespace std; 

const int MAXN = 100005;

int a[MAXN];
int lg[MAXN];
int Max[MAXN][22];
int n,Num = 1,m; 

void Init() {
	lg[0] = -1;
	for(int i = 1;i <= n;++i) lg[i] = lg[i >> 1] + 1;
	for(int j = 1;j <= 21;++j)	
		for(int i = 1;i + (1 << j) - 1 <= n;++i)	
			Max[i][j] = max(Max[i][j - 1],Max[i + (1 << (j - 1))][j - 1]);
}

int Query(int l,int r) {
	int k = lg[r - l + 1];
	return max(Max[l][k],Max[r - (1 << k) + 1][k]);
}

int main() {
	freopen("query.in","r",stdin);
	freopen("query.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i) scanf("%d",a + i);
	for(int i = n;i >= 1;--i) {
		if(a[i] == a[i + 1]) Num++;
		else Num = 1;
		Max[i][0] = Num; //到达每一个点时每一个数的最大出现次数 
	}
	Init();
	for(int i = 1;i <= m;++i) {
		int l,r;
		scanf("%d%d",&l,&r); //分段 分l->a[l]最后出现的位置 a[r]一开始出现的位置->r  和  完整的区间进行ST查询 
		int pos2 = lower_bound(a + 1,a + 1 + n,a[r]) - a; // 返回当前a[r]一开始出现的位置
		if(pos2 < l) pos2 = l; //特判一下,如果a[r]一开始出现的位置比l小,我们是不可取pos2的 
		//if(i == m) cout << l << " " << r << " " << pos2 << endl;
		int Ans = r - pos2 + 1; //一开始定义Ans为a[r]出现的次数 
		r = pos2 - 1; //找下一个完整区间的数 
		if(l > r) { //如果l比r大了,那么我们找完了所有区间 
			printf("%d\n",Ans);
			continue;
		}
		Ans = max(Ans,Query(l,r));  //否则我们寻找完整的区间进行ST 
		printf("%d\n",Ans);
	}
	return 0;
}

 

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注