完美数列(PAT)

完美数列(PAT)

题目描述
给定一个正整数数列,和正整数p,设这个数列中的最大值是M,最小值是m,如果M <= m * p,则称这个数列是完美数列。现在给定参数p和一些正整数,请你从中选择尽可能多的数构成一个完美数列。
输入描述:
输入第一行给出两个正整数N和p,其中N(<= 1e5)是输入的正整数的个数,p(<= 1e9)是给定的参数。第二行给出N个正整数,每个数不超过1e9。
输出描述:
在一行中输出最多可以选择多少个数可以用它们组成一个完美数列。
输入例子:
10 8
2 3 20 4 5 1 6 7 8 9
输出例子:
8
解题思路:

本题可采取sort排序+二分的方法进行解答,具体分析如下:

  1. 题意:获取一个完美数列,该数列中最大值M,最小值m,满足M<=m*p。由此可知,我们的关注点在于获得一个最大值和一个最小值,使得该两最值在满足上述公式的前提下,两者相距尽可能最长(两者中间需包含尽可能多的数)。

  2. 如何方便地获得任意两对相距最长的最大值和最小值呢?我们采取sort将所有数进行排序,而后选取任一数作为最大值M(不能直接选取整个输入数列的最大值或最小值,因为这样会进入局部最优情况,而非全局最优,因为每一个数均有可能为最长完美数列的最大值),那么我们根据给定的p值,我们可得知相应满足条件的最小m值为ceil(M/p),例如20和8,满足的m值为3。

  3. 接下来找出满足条件的m值位置pos,而最大值和最小值之间的元素个数(因为有序,所以能通过该方法获取完美数列长度)便是该完美数列的长度。而找出满足条件的m值,由于我们采取了sort对数据进行了排序,所以我们可以使用时间复杂度为log(n)的二分算法查找不小于m的数位置,最后计算:
    以 M 为 最 大 值 的 完 美 数 列 长 度 = M 下 标 − m 下 标 + 1 以M为最大值的完美数列长度 = M下标 - m下标 + 1 M=Mm+1

  4. 最后,定义遍历Max,为获取的最长完美数列长度,对于每次获取的完美数列长度,取最大值。

源代码:
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 1e5 + 50; 

int n, p;

int num[N];			//用于记载以当前数为最大值所拥有的完美数列长度 
int Max = -1;		//最长长度 
vector<int> v;		//vector容器存储数组 

//从left和right区间,二分算法查找不小于num数的位置 
int erf(int num, int left, int right) {
	int l = left, r = right;
	int mid = (l+r) / 2;
	
	while(l <= r) {
		//相等或大于时r = mid-1,说明最后退出循环时,不小于num数位置刚好为r+1 
		if(v[mid] >= num) {
			r = mid - 1; 
		}
		else {
			l = mid + 1;
		}
		
		mid = (l+r) / 2;			//更新mid 
	}
	
	//返回r+1
	return r+1;
} 

int main() {
	scanf("%d%d", &n, &p);
	
	//输入数据
	for(int i = 0; i < n; i++) {
		int a;
		scanf("%d", &a);			//数据较多,采用scanf输入 
		v.push_back(a);				
	}
	
	//数据量大小1e5,可采取最简单的sort进行数据排序
	sort(v.begin(), v.end()); 
	
	num[0] = 1;		//第一个数一定满足M <= m*p
	 
	//从1开始遍历全部数据(下标方便取),获取完美数列长度 
	for(int i = 1; i < n; i++) {
		//其完美数列左端必须不小于Min 
		int Min;
		
		//如果刚好整除,则Min为v[i]/p,否则进一(满足条件M <= m*p) 
		if(v[i]%p == 0) Min = v[i] / p;
		else Min = v[i] / p + 1;
		
		//调用二分函数,从0~i-1位置获取不小于Min的数的下标位置 
		int pos = erf(Min, 0, i-1); 
		
		//完美数列长度:M所在下标 - m所在下标 + 1
		num[i] = i - pos + 1;
		
		//找出最长长度完美数列
		Max = max(Max, num[i]);		 
	}
	
	cout << Max;		//输出结果 
	return 0;
} 
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值