题目链接在此。
本题目前有二分查找法、two pointers法。
题意
从N个正整数中选出若干个数,使得选出的这些数中的最大值M和最小值m满足M<=m*p。问满足条件的选择方案中,选出的数的最大个数。
二分查找法
思路
首先需要对N个正整数从小到大排序,然后需要明白,选出的数个数最大的方案,一定是在该递增序列中连续的若干序列(这里不做证明,很好想)。
首先,很容易想到的是从左往右扫描,对于每个a[i],在a[i+1]~a[n-1]中找到第一个大于a[i]*p的数的位置index,这样index-i就是满足条件的最远长度。
但是这样两遍线性扫描的时间复杂度太大,所以需要将在a[i+1]~a[n-1]找位置换成二分查找。
注意点:
1. 数据范围会超过int,索性都用long long
2. 当数组a[]中所有数都超过了a[i]*p的情况进行特判
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long n, a[100010], p;
int main(){
scanf("%lld %lld",&n, &p);
for(int i = 0; i < n; i++){
scanf("%lld",&a[i]);
}
sort(a,a+n); //从小到大排序
int ans = 1;
int i;
int left, right;
for(i = 0; i < n; i++){
long long mp = a[i]*p; //mp 对应题干m*p
left = i+1, right = n-1;
if(a[right] < mp){ //如果数组中最大的数都小于mp,则从i到最后一个数都满足
right = n;
}else{
while( left < right){ //二分查找
int mid = (left+right)/2;
if(a[mid] <= mp){ //a[mid]<=要找的数,则说明第一个大于要找的数在mid的右边
left = mid+1;
}else{
right = mid;
}
}
}
ans = max(ans,right-i);
}
printf("%d\n",ans);
return 0;
}
two pointers法
思路
首先,很容易得到:如果a[M]<=a[m]*p成立,那么对于[m,M]内的任意位置k,一定也有a[k]<=a[m]*p。这种有序序列的性质就引导我们往two pointers思想去思考:
首先对输入进行从小到大的排序,然后令两个下标m,M,都从0开始,并设置计数器count保存满足a[M]<=a[m]*p的最大长度。
接下来让M不断增加,知道不等式a[M]<=a[m]*p不成立为止,在此过程中更新count。之后让m右移一位,比继续上面让j不断增加的操作,依次类推,直到M到达序列末端。
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,p;
long long a[100010];
int main(){
scanf("%d %d",&n,&p);
for(int i = 0; i < n; i++){
scanf("%lld",&a[i]);
}
sort(a,a+n);
int m = 0,M = 0;
long long sum ;
int count = 1;
while( m < n){
sum = a[m]*p;
while(a[M] <= sum && M < n){
count = max(count, M-m+1);
M++;
}
m++;
}
printf("%d\n",count);
return 0;
}