二分查找技巧---字节跳动2018校招算法方向(第二批)---用户喜好
时间限制:3秒
空间限制:262144K
为了不断优化推荐效果,今日头条每天要存储和处理海量数据。假设有这样一种场景:我们对用户按照它们的注册时间先后来标号,对于一类文章,每个用户都有不同的喜好值,我们会想知道某一段时间内注册的用户(标号相连的一批用户)中,有多少用户对这类文章喜好值为k。因为一些特殊的原因,不会出现一个查询的用户区间完全覆盖另一个查询的用户区间(不存在L1<=L2<=R2<=R1)。
输入描述:
输入: 第1行为n代表用户的个数 第2行为n个整数,第i个代表用户标号为i的用户对某类文章的喜好度 第3行为一个正整数q代表查询的组数 第4行到第(3+q)行,每行包含3个整数l,r,k代表一组查询,即标号为l<=i<=r的用户中对这类文章喜好值为k的用户的个数。 数据范围n <= 300000,q<=300000 k是整型
输出描述:
输出:一共q行,每行一个整数代表喜好值为k的用户的个数
输入例子1:
5 1 2 3 3 5 3 1 2 1 2 4 5 3 5 3
输出例子1:
1 0 2
例子说明1:
样例解释: 有5个用户,喜好值为分别为1、2、3、3、5, 第一组询问对于标号[1,2]的用户喜好值为1的用户的个数是1 第二组询问对于标号[2,4]的用户喜好值为5的用户的个数是0 第三组询问对于标号[3,5]的用户喜好值为3的用户的个数是2
解题思路:
刚开始看到题目,觉得是区间查询的题目,于是想线段树、RMQ那些,结果发现查找的k值不能预先存为状态。搜了一下题解发现只是用到二分查找的技巧,看看下图就明白了:
为了进行二分查找,我们对喜好值进行排序,但是注意到有查询区间,所以标号也必须一起排序。需要注意的是在喜好值相等的片段里标号也要排成递增次序,这个可以写个结构体(记为Num)然后重写比较函数。那么在标号按递增次序后,我们可以发现,如果要查找[2,6]区间里喜好值为2的人数,只需要二分查找k=2,L=2的上界,以及k=2,R=6的下界
(也可以理解为先二分查找k所在的区间,然后二分查找L和R的位置,然后取其长度,但实际上只需要两次二分查找就行了,毕竟排序函数如果可以用比较函数把数组排好序,那么同样可以只用一次二分查找把边界找出来,因为它们都用到自定义的比较函数)
,如图找到下界4和上界6,那么上界与下界之间的长度即为[2,6]之间的喜好值为2的人数。
需要用到的是二分查找的函数,这里复习一下:
用lower_bound
找到第一个大于或等于左端点的位置,
用upper_bound
找到最后一个小于或等于右端点的位置
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 6 const int maxn=300000+5; 7 8 struct Num{ 9 int id; 10 int val; 11 bool operator<(const Num &b)const{ 12 if(val==b.val)return id<b.id; 13 else return val<b.val; 14 } 15 }num[maxn]; 16 17 int main() 18 { 19 int n; 20 scanf("%d",&n); 21 for(int i=0;i<n;i++){ 22 scanf("%d",&num[i].val); 23 num[i].id=i; 24 } 25 sort(num,num+n); 26 int q; 27 scanf("%d",&q); 28 while(q--){ 29 int l,r,k; 30 scanf("%d%d%d",&l,&r,&k); 31 l--; r--; 32 Num L,R; 33 L.id=l; R.id=r; 34 L.val=R.val=k; 35 int ansL=lower_bound(num,num+n,L)-num; 36 int ansR=upper_bound(num,num+n,R)-num; 37 printf("%d\n",ansR-ansL); 38 } 39 40 return 0; 41 }