題目描述
給定一個正整數數列,和正整數 p,設這個數列中的最大值是 M,最小值是 m,如果 M≤mp,則稱這個數列是完美數列。
現在給定參數 p 和一些正整數,請你從中選擇儘可能多的數構成一個完美數列。
輸入格式
輸入第一行給出兩個正整數 N 和 p,其中 N(≤105)是輸入的正整數的個數,p(≤109)是給定的參數。第二行給出 N 個正整數,每個數不超過 109。
輸出格式
在一行中輸出最多可以選擇多少個數可以用它們組成一個完美數列。
輸入樣例
10 8
2 3 20 4 5 1 6 7 8 9
輸出樣例
8
題目信息
作者:CAO, Peng
單位:Google
代碼長度限制:16 KB
時間限制:200 ms
內存限制:64 MB
題目類型:二分-尋找有序數列第一個滿足某條件的元素的位置
分析
由於題幹中涉及序列的最大值和最小值,因此不妨先將所有N個正整數從小到大進行排序。
在此基礎上能證明:能使選出的數個數最大的方案,一定是在該遞增序列中選擇連續的若干個數的方案。
(如果不想看證明可以跳過)
我們簡單解釋一下反證法是如何證明結論的:
首先假設能使選出的數個數最大的方案,不是在該遞增序列中連續的方案。
那就舉個例子: 存在序列 1 2 3 4 5 6 7,我們假設1 2 3 7 滿足 M<=m*p的要求,根據假設,1 2 3 7 是滿足條件的最大序列,可是當我們取出 1 2 3 4 5 6 7,M還是7,m還是1,既然1 2 3 7滿足條件,那麼 1 2 3 4 5 6 7一定滿足條件,這就與假設矛盾,假設不成立
數學上的反證法爲:
證明:設遞增序列A爲{a1,a2,……,ai,ai+1,……,ai+m,aj,……,an },假設從中能選出的個數最大的方案爲{ ai,ai+1,……,ai+m,aj },即ai,ai+1,ai+m 爲序列中連續的數,aj 爲與其在序列中不連續的數。因此該方案中的最大值爲 aj,最小值爲ai,且滿足 aj≤ai*p。而事實上,由於原序列A中元素是遞增的,因此可以把方案擴充爲連續序列(ai,ai+1,……,aj},而這個序列的最大值仍然爲 aj,最小值仍然爲 ai;,因此 aj≤ ai*p 仍然成立。於是就找到了一個新的序列,使得它的長度比原先不連續序列的長度要長。因此假設不成立,得出結論:能使選出的數的個數最大的方案,一定是在該遞增序列中選擇連續的若干個數的方案。證畢。
從左至右掃描序列,對其中的每一個數a[i],在a[i+1]~a[n-1]內二分查找第一個超過a[i]*p的數的位置j,這樣 j-i 就是對位置 i 來說滿足 M < np 的最遠長度。取所有j-i 的最大值即爲所求的答案,時間複雜度爲 O(logn)。
注意點
主要是JAVA的注意點,數量級有 109,所以要注意範圍問題,由於 Java 的 Integer.MAX_VALUE 爲 2147483647,所以大多數地方無需擔心,只要注意這兩個地方
- 首先是取中點的位置,可以把
(left + right)/2
,換成left + (right - left)/2
- 取中點時的 right 要在數組最後一個元素的後面,這樣是爲了防止二分查找沒有能超過a[i]*p的數時,卻返回了數組最後一個元素的情況
- 還有就是 p * N 很可能會超過 Java 所能表示的長度,所以這裏要用 Long,否則測試點5會錯誤
代碼實現-JAVA
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
class Main{
public static void main(String[] args) throws Exception{
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
String[] strArr;
strArr = bf.readLine().split(" ");
int N = Integer.parseInt(strArr[0]);
long p = Long.parseLong(strArr[1]);
strArr = bf.readLine().split(" ");
long[] longArr = new long[N];
for(int i =0; i<N; i++)
longArr[i] = Long.parseLong(strArr[i]);
// 排序
Arrays.sort(longArr);
System.out.println(findMaxLength(longArr, p));
}
// 找到第一個比arr[i]更大的數的位置
// 參數 x 是 arr[i] * p,會超過 int 表示範圍
public static long findFirstBig( long[] arr,long x ) {
int left = 0;
// 不是arr.length-1,right的位置在最後一個元素的後面
int right = arr.length;
while(left < right) {
// 避免超過int表示範圍
int mid = left + (right - left)/2;
if(arr[mid] > x) {
right = mid;
}else {
left = mid + 1;
}
}
return left;
}
// 返回最大長度
public static long findMaxLength( long[] arr, long p ){
long max = 0;
for(int i=0; i<arr.length; i++) {
// arr[i] * p 會超過 int 表示範圍
long nowMaxPos = findFirstBig(arr, arr[i]*p);
if(nowMaxPos - i > max)
max = nowMaxPos - i;
}
return max;
}
}
參考:《算法筆記》胡凡