题目
给定一个正整数数列,和正整数p,设这个数列中的最大值是M,最小值是m,如果M <= m * p,则称这个数列是完美数列。 现在给定参数p和一些正整数,请你从中选择尽可能多的数构成一个完美数列。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
bool cmp(int a, int b) {
return a < b;
}
void func(int a[], int n, int p) {
int num = 0;
for (int i = 0; i < n; i++) {
long m = p * a[i];
int n1 = 0;
for (int j = i; j < n; j++) {
if (a[j] <= m) {
n1++;
}
}
if (cmp(num, n1))num = n1;
}
printf("%d", num);
}
int main() {
// freopen("in.in", "r", stdin);
int n, p;
scanf("%d %d", &n, &p);
int a[n];
for (int i = 0; i < n; i++) {
scanf("%d",a+i);
}
sort(a, a + n, cmp);
func(a, n, p);
return 0;
}
这题思路如果按照以上的解题方式简单,时间复杂度就是O(n^2)。
对于数组输入的方式,如果将scanf输入换成cin的输入方式,可能会出现超时的情况。这里放的解法是我想到的比较容易实现的。关于其他解法,我想到二分查找的方法,但是没有实现,卡在了中间数a[mid]和m*p那里,不知道怎么判断。
参考以下的文章,low返回的就是能够到达的最大数列中不超过m*p的数字的下表。这个也需要分析才能理解,对于题目使用的二分查找的方法时间复杂度为O(nlogn)
二分查找解题思路
参考文件的多种解法出处PAT-Basic 完美数列(25) 二分法和双指针c++解题思路_牛客网 (nowcoder.com)
/*
* app=PAT-Basic lang=c++
* https://pintia.cn/problem-sets/994805260223102976/problems/994805291311284224
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100010;
int a[maxn] = {};
int N, p;
/* 二分法:
* 注意数据边界,10^9很容易超过int范围,所以用long long
*/
int binarySearch(int low,long long m){
int high = N - 1;
while (low <= high){
int mid = low + (high - low) / 2;
if (a[mid] <= m*p)
low = mid + 1;
else
high = mid - 1;
}
return low;
}
int main()
{
scanf("%d%d", &N, &p);
for (int i = 0; i < N; i++){
scanf("%d",&a[i]);
}
sort(a,a+N);
int maxNum = 0;
for (int i = 0; i < N; i++){
int low = binarySearch(i, a[i]);
maxNum = low - i > maxNum ? low - i : maxNum;
}
printf("%d",maxNum);
return 0;
}
二分查找扩展
基于二分查找,可以进一步扩展两个方法。
-
查找第一个大于或等于x的元素位置
-
查找第一个大于x的元素位置
文章里面还有一种双指针方法解题思路:
双指针解题:
/*
* app=PAT-Basic lang=c++
* https://pintia.cn/problem-sets/994805260223102976/problems/994805291311284224
*/
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100010;
int a[maxn] = {};
int main()
{
/*双指针法*/
int N, p;
long long mp;
scanf("%d%d",&N,&p);
for (int i = 0; i < N;i++){
scanf("%d", &a[i]);
}
sort(a, a + N);
int max = 0,high = 0;//high赋值一次即可
for (int i = 0; i < N;i++){
mp = (long long)a[i] * p;
while (high < N){
if (a[high] <= mp) high++;//对于后面的数来说,a[high]要么是第一个大于mp的数,要么是小于等于mp的数。
else break;
}
max = max > high - i? max : high - i;
}
printf("%d",max);
return 0;
}
测试用例不过的情况
1.没有看来到数组的长度全部符合完美数组的情况,在输出的时候可能出现完美数组总数为0的情况,需要考虑代码的计算总数的(第一个测试点和第四个测试点)
2.使用的整型类型小于m*p的大小,造成m*p的值不准确,比较大小出错,建议使用longlong类型
数列使用longlong类型适合比较。(第5测试点)
3.我实现的第一个代码在牛客网能够通过,但是在pat官网刷题的时候发现不行,出现了超时的情况,改进的代码不会出现超时,原理和上面我写的双指针的解决方式是一样的。但是这个我在看“区间贪心”的解决方式时候突然想起来这个可以当初由 a[i]<=完美数列<=mp=a[i]*p 来组成的区间来解决
改进代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
freopen("in.txt","r",stdin);
int n,p;
scanf("%d %d",&n,&p);
long long a[n];//longlong
for(int i=0;i<n;i++){
scanf("%lld",a+i);//输入格式为lld
}
int sum=0,k=0;
sort(a,a+n);
for(int i=0;i<n;i++){
int num=k-i;
long long mp=a[i]*p;
for(;k<n;k++){
if(a[k]>mp){
break;
}
num++;
}
sum=sum>num?sum:num;
}
printf("%d",sum);
return 0;
}