faebdc的烦恼
二分查找+RMQ(ST表实现)
Luogu P1997
题目描述
给出一个升序排列的整数数组a1,a2,…an,你的任务是对于鸟哥的一系列询问(i,j),回答ai,ai+1,…aj中出现次数最多的值所出现的次数。
输入输出格式
输入格式:
输入仅包含一组数据。
第一行为两个整数n,q(1<=n<=100000,1<=q<=200000)。第二行包含n个升序排列的整数a1,a2,…,an(-100000<=ai<=100000),代表每一道题的难度值。以下q行每行包含两个整数i和j(1<=i<=j<=n),代表询问的区间。
输出格式:
对于每次询问,单独输出一行,该行仅有一个整数,表示该区间内出现最多的数值所出现的次数。
输入输出样例
输入样例#1:
9 1
1 1 1 2 2 3 3 4 4
3 8
输出样例#1:
2
说明
各个测试点1s
二分查找+RMQ(ST表实现)
题目简化后如下:给定一个升序数列和多次询问,求询问区间内的众数出现的次数。
看到这个题,我的第一想法就是序列的元素是多少不重要,重要的是每种元素的个数,所以我们就记录左右端点和它的长度,长度另开一个数组来存,然后我们对询问的区间内哪个数出现次数最多进行运算。
自然的,我们想求出左右端点的数字a[l],a[r]是第几种数字,所以我们考虑二分。
而它们又并不一定在这个区间内全部出现,所以我们将二分出的种类序号对应的左(右)端点求出来,作一个差,把a[l]和a[r]在区间内出现的次数求出来,求一个最大值。
接下来我们就是把区间内全组完整存在的数出现的次数的最大值求出来,这里我们就可以用RMQ来logn查询区间最大值,然后在更新刚才的最大值即可,这里我的RMQ用了ST表来实现,当然也能用线段树。
举个例子吧:
如样例所示
9 1
1 1 1 3 3 5 5 7 7
3 8
令p=3,q=8
由于1出现3次,3出现2次,5出现2次,7出现2次,所以个数数组为:3 2 2 2,然后我们先初始化掉RMQ的ST表。
a[1].left=1,a[1].right=3
a[2].left=4,a[2].right=5
a[3].left=6,a[3].right=7
a[4].left=8,a[4].right=9
我们求区间【3,8】,所以首先二分求出位置3所在的组数,即为第1组,同理位置8在第4组。
然后第1组在区间内有a[1].right-p+1=1个数;
然后第4组在区间内有a[4].left-q+1=1个数;
然后我们只需要求[p+1,q-1]组之间的出现次数的最大值,用ST表做一个RMQ问题就好了。
值得注意的一点就是,如果询问区间是某一组所在区间的真子集,比如本例中询问[1,2],那么我们更新就应该更新a[1].right-a[1].left+1,而不是a[1].right-p+1与a[4].left-q+1。这一点一定注意,如果没有的话是51分(第一次没有AC就是这样的)。
时间复杂度上表现不是很好:最差情况下是O(M*3logN)。
推介一波博客Stockholm_Sun萌萌的博客
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int i,j,m,n,t,temp;
int a[100001];
int map[100001][20];
struct data
{
int ll,rr;
}b[100001];
int r()//读入优化
{
int ans=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ans*=10;
ans+=ch-'0';
ch=getchar();
}
return ans*f;
}
int erfen1(int x)//两个二分
{
int mid,le=1,ri=temp,ans;
while(le<=ri)
{
mid=le+ri;
mid>>=1;
if(b[mid].ll<x) ans=mid,le=mid+1;
else if(b[mid].ll>x) ri=mid-1;
else return mid;
}
return ans;
}
int erfen2(int x)
{
int mid,le=1,ri=temp,ans;
while(le<=ri)
{
mid=le+ri;
mid>>=1;
if(b[mid].rr<x) le=mid+1;
else if(b[mid].rr>x) ans=mid,ri=mid-1;
else return mid;
}
return ans;
}
void work()//ST表的构建
{
int i,j;
for(j=1;1<<j<=n;j++)
for(i=1;i+(1<<j)-1<=n;i++)
map[i][j]=max(map[i][j-1],map[i+(1<<j-1)][j-1]);
}
int question(int z,int y)//区间查询最大值
{
int x=(int)(log(y-z+1)/log(2));
return max(map[z][x],map[y-(1<<x)+1][x]);
}
int main()
{
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
n=r(),m=r();
b[++temp].ll=1;
a[1]=r();
for(i=2;i<=n;i++)
{
a[i]=r();
if(a[i]!=a[i-1])
{
b[temp].rr=i-1;
b[++temp].ll=i;
}
}
b[temp].rr=n;
for(i=1;i<=temp;i++)
{
map[i][0]=b[i].rr-b[i].ll+1;//初始化ST表
}
work();
int p,q,maxx=0;
for(i=1;i<=m;i++)
{
maxx=0;
p=r(),q=r();
int st=erfen1(p);//这两种二分一定要区分开
int en=erfen2(q);
maxx=max(maxx,min(b[st].rr-p+1,q-p+1));//特殊情况判断
maxx=max(maxx,min(q-b[en].ll+1,q-p+1));
if(st+1<=en-1)
maxx=max(maxx,question(st+1,en-1));
cout<<maxx<<endl;
}
return 0;
}
/*
*/