PAT-B1030/A1085-完美數列 (25分)

在這裏插入圖片描述

題目描述

給定一個正整數數列,和正整數 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;
	}
}

參考:《算法筆記》胡凡
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章