洛谷: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;
}
文章评论