【精品計劃0】藍橋杯 摔手機

原題描述:

????????x星球的居民脾氣不太好,但好在他們生氣的時候唯一的異常舉動是:摔手機。
各大廠商也就紛紛推出各種耐摔型手機。x星球的質監局規定了手機必須經過耐摔測試,并且評定出一個耐摔指數來,之后才允許上市流通。
????????x星球有很多高聳入云的高塔,剛好可以用來做耐摔測試。塔的每一層高度都是一樣的,與地球上稍有不同的是,他們的第一層不是地面,而是相當于我們的2樓。
????????如果手機從第7層扔下去沒摔壞,但第8層摔壞了,則手機耐摔指數=7。

特別地,如果手機從第1層扔下去就壞了,則耐摔指數=0。如果到了塔的最高層第n層扔沒摔壞,則耐摔指數=n

????????為了減少測試次數,從每個廠家抽樣3部手機參加測試。
????????某次測試的塔高為1000層,如果我們總是采用最佳策略,在最壞的運氣下最多需要測試多少次才能確定手機的耐摔指數呢?
????????請填寫這個最多測試次數。

????????注意:需要填寫的是一個整數,不要填寫任何多余內容

答案19

?

文章目的

?

讀完題后,我們追求的不是要寫出得數(至少對于本博客是不夠的),而是用代碼實現方法,并思考是否可以優化。

其實本題的方法是非常多種多樣的。非常適合鍛煉思維。

我們把問題擴展到n個手機來思考。

手機k個,樓n層,最終結果M次。

時空復雜度目錄

暴力:? ? ? ? ? ? ? ? ? ? ? ? O(N!)

DP:? ? ? ? ? ? ? ? ? ? ? ? ? ? O(N*N*K)? O(N*K)

壓空間:? ? ? ? ? ? ? ? ? ? O(N*N*K)? O(N)

四邊形不等式優化? ? ?O(N*N)? ? ? ?

換思路:? ? ? ? ? ? ? ? ? ? O(K*M)

最優:? ? ? ? ? ? ? ? ? ? ? ? ?O(K*M)? ? O(N)

文末有測試,大家可以去直觀感受一下各方法運行的效率

二分

?

容易想到二分思路:不斷二分范圍,取中點,測驗是否會摔壞,然后縮小一半范圍,繼續嘗試,很顯然,答案為logN(2為底)

但是,二分得出的答案是不對的。注意:我們要保證在都手機摔完之前,能確定耐摔指數到底是多少。

舉例:

我們在500樓摔壞了,在250樓摔,又壞了。接下來,我們只能從1樓開始一層一層試

因為如果我們在125層摔壞了,就沒有手機可以用,也就是永遠都測不出來,而這是不被允許的。其實我們連測第2層都不敢測,因為在第2層摔壞了,我們就無法知道手機在第一層能否被摔壞。所以只有一部手機時,我們只敢從第一層開始摔。

?

嘗試較優的策略

?

既然二分是不對的,我們繼續分析:摔手機的最優策略到底是如何的。

只有一部手機時,我們只敢從第一層開始摔。

有兩部手機的時候,我們就敢隔層摔了,因為一部手機壞了,我們還有另一部來繼續試。

這時就有點意思了,我們分析情況:

情況1)假設我們第一部手機在i層摔壞了,然后最壞情況還要試多少次?這時我們還剩一部手機,所以只敢一層一層試,最壞情況要試到i-1層,共試了i次。

情況2)假設我們第一部手機在i層試了,但是沒摔壞,然后最壞情況還要試多少次?(這時發現算情況2時依舊是相似的問題,確定了可以用遞歸來解。)

?

最優解(最小值)是決策后兩種情況的最差情況(最大值),我們的本能感覺應該就是讓最差情況好一點,讓最好情況差一點,這樣比較接近正確答案。比如兩部手機,一百層,我們可以在50層摔,沒壞,這一次就很賺,我們沒摔壞手機還把范圍縮小了50層。如果壞了,就比較坑了,我們要從1試到50。雖然可能縮小一半,但是最壞情況次數太多,所以肯定要從某個低于五十的層開始嘗試。

(以上幾行是為了讓讀者理解決策,下面正文分析)

?

歸納表達式

?

假設我們的樓一共n層,我們的i可以取1-n任意值,有很多種可能的決策,我們的最小值設為f(n,k),n代表樓高(范圍為1-100或101-200其實都一樣),k代表手機數.

我們假設的決策是在第i樓扔

對于情況一,手機少了一部,并且我們確定了范圍,一定在第i樓以下,所以手機-1,層數為i-1,這時f(n,k)=f(i-1,k-1).+1

對于情況二,手機沒少,并且我們確定了范圍,一定在第i樓之上,所以手機數不變,而層數-i層,這時f(n,k)=f(n-i,k).+1

歸納出

f(n,k)=min(? max(f(i-1,k-1) ,f(n-i,k)?)?i取1-n任意數 ? ?)+1

簡單總結:怎么確定第一個手機在哪扔?每層都試試,哪層的最壞情況(max)最好(min),就去哪層扔。

?

寫出暴力遞歸

按照分析出來的表達式,我們可以寫出暴力遞歸:

	public static int solution1(int nLevel, int kChess) {if (nLevel == 0) {return 0;}//范圍縮小至0if (kChess == 1) {return nLevel;}//每層依次試int min = Integer.MAX_VALUE;//取不影響結果的數for (int i = 1; i != nLevel + 1; i++) {//嘗試所有決策,取最優min = Math.min(min,Math.max(Process1(i - 1, kChess - 1),Process1(nLevel - i, kChess)));}return min + 1;//別忘了加上本次}

?

改為動態規劃

?

具體思路如下

https://blog.csdn.net/hebtu666/article/details/79912328

	public static int solution2(int nLevel, int kChess) {if (kChess == 1) {return nLevel;}int[][] dp = new int[nLevel + 1][kChess + 1];for (int i = 1; i != dp.length; i++) {dp[i][1] = i;}for (int i = 1; i != dp.length; i++) {for (int j = 2; j != dp[0].length; j++) {int min = Integer.MAX_VALUE;for (int k = 1; k != i + 1; k++) {min = Math.min(min,Math.max(dp[k - 1][j - 1], dp[i - k][j]));}dp[i][j] = min + 1;}}return dp[nLevel][kChess];}

?

壓縮空間

?

我們發現,對于狀態轉移方程,只和上一盤排的dp表和左邊的dp表有關,所以我們不需要把值全部記錄,用兩個長度為n的數組不斷更新即可(具體對dp壓縮空間的思路,也是很重要的,我在其它文章中有提過,在這里就不寫了)

	public static int solution3(int nLevel, int kChess) {if (kChess == 1) {return nLevel;}int[] preArr = new int[nLevel + 1];int[] curArr = new int[nLevel + 1];for (int i = 1; i != curArr.length; i++) {curArr[i] = i;}//初始化for (int i = 1; i != kChess; i++) {//先交換int[] tmp = preArr;preArr = curArr;curArr = tmp;//然后打新的一行for (int j = 1; j != curArr.length; j++) {int min = Integer.MAX_VALUE;for (int k = 1; k != j + 1; k++) {min = Math.min(min, Math.max(preArr[k - 1], curArr[j - k]));}curArr[j] = min + 1;}}return curArr[curArr.length - 1];}

?

四邊形不等式優化

?

四邊形不等式是一種比較常見的優化動態規劃的方法

定義:如果對于任意的a1≤a2<b1≤b2,有m[a1,b1]+m[a2,b2]≤m[a1,b2]+m[a2,b1],那么m[i,j]滿足四邊形不等式。

對s[i,j-1]≤s[i,j]≤s[i+1,j]的證明:

設mk[i,j]=m[i,k]+m[k,j],s[i,j]=d

對于任意k<d,有mk[i,j]≥md[i,j](這里以m[i,j]=min{m[i,k]+m[k,j]}為例,max的類似),接下來只要證明mk[i+1,j]≥md[i+1,j],那么只有當s[i+1,j]≥s[i,j]時才有可能有mk[i+1,j]≥md[i+1,j]

(mk[i+1,j]-md[i+1,j])-(mk[i,j]-md[i,j])

=(mk[i+1,j]+md[i,j])-(md[i+1,j]+mk[i,j])

=(m[i+1,k]+m[k,j]+m[i,d]+m[d,j])-(m[i+1,d]+m[d,j]+m[i,k]+m[k,j])

=(m[i+1,k]+m[i,d])-(m[i+1,d]+m[i,k])

∵m滿足四邊形不等式,∴對于i<i+1≤k<d有m[i+1,k]+m[i,d]≥m[i+1,d]+m[i,k]

∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0

∴s[i,j]≤s[i+1,j],同理可證s[i,j-1]≤s[i,j]

證畢

?

通俗來說,

優化策略1)我們在求k+1手機n層樓時,最后發現,第一個手機在m層扔導致了最優解的產生。那我們在求k個手機n層樓時,第一個手機的策略就不用嘗試m層以上的樓了。

優化策略2)我們在求k個手機n層樓時,最后發現,第一個手機在m層扔導致了最優解的產生。那我們在求k個手機n+1層樓時,就不用嘗試m層以下的樓了。

	public static int solution4(int nLevel, int kChess) {if (kChess == 1) {return nLevel;}int[][] dp = new int[nLevel + 1][kChess + 1];for (int i = 1; i != dp.length; i++) {dp[i][1] = i;}int[] cands = new int[kChess + 1];for (int i = 1; i != dp[0].length; i++) {dp[1][i] = 1;cands[i] = 1;}for (int i = 2; i < nLevel + 1; i++) {for (int j = kChess; j > 1; j--) {int min = Integer.MAX_VALUE;int minEnum = cands[j];int maxEnum = j == kChess ? i / 2 + 1 : cands[j + 1];//優化策略for (int k = minEnum; k < maxEnum + 1; k++) {int cur = Math.max(dp[k - 1][j - 1], dp[i - k][j]);if (cur <= min) {min = cur;cands[j] = k;//最優解記錄層數}}dp[i][j] = min + 1;}}return dp[nLevel][kChess];}

注:對于四邊形不等式的題目,比賽時不需要嚴格證明

通常的做法是打表出來之后找規律,然后大膽猜測,顯然可得。(手動滑稽)

?

換一種思路

?

有時,最優解并不是優化來的。

當你對著某個題冥思苦想了好久,無論如何也不知道怎么把時間優化到合理范圍,可能這個題的最優解就不是這種思路,這時,試著換一種思路思考,可能會有奇效。

(比如訓練時一道貪心我死活往dp想,肝了兩個小時以后,不主攻這個方向的隊友三分鐘就有貪心思路了,淚目,不要把簡單問題復雜化

?

我們換一種思路想問題:

原問題:n層樓,k個手機,最多測試次數

反過來看問題:k個手機,扔m次,最多能確定多少層樓?

我們定義dp[i][j]:i個手機扔j次能確定的樓數。

分析情況:依舊是看第一部手機在哪層扔的決策,同樣,我們的決策首先要保證能確定從1層某一段,而不能出現次數用完了還沒確定好的情況。以這個為前提,保證了每次扔的樓層都是最優的,就能求出結果。

依舊是取最壞情況:min(情況1,情況2)

情況1)第一個手機碎了,我們就需要用剩下的i-1個手機和j-1次測試次數往下去測,dp[i-1][j-1]。那我們能確定的層數是無限的,因為本層以上的無限層樓都不會被摔壞。dp[i-1][j-1]+無窮=無窮

情況2)第一個手機沒碎,那我們就看i個手機扔j-1次能確定的樓數(向上試)+當前樓高h

歸納表達式,要取最差情況,所以就是只有情況2:dp[i][j]=dp[i-1][j-1]+h

那這個h到底是什么呢?取決于我敢從哪層扔。因為次數減了一次,我們還是要考慮i個球和j-1次的最壞情況能確定多少層,我才敢在層數+1的地方扔。(這是重點)

也就是dp[i][j-1]的向上一層:h=dp[i][j-1]+1

?

總:min(情況1,情況2)=min(無窮,dp[i-1][j-1]+dp[i][j-1]+1)=dp[i-1][j-1]+dp[i][j-1]+1

這是解決k個手機,扔m次,最多能確定多少層樓?

原問題是n層樓,k個手機,最多測試次數。

所以我們在求的過程中,何時能確定的層數大于n,輸出扔的次數即可

?

最優解

我們知道完全用二分扔需要logN+1次,這也絕對是手機足夠情況下的最優解,我們做的這么多努力都是因為手機不夠摔啊。。。。所以當我們的手機足夠用二分來摔時,直接求出logN+1即可。

?

當然,我們求dp需要左邊的值和左上的值:

依舊可以壓縮空間,從左往右更新,previous記錄左上的值。

求自己時也要注意記錄,否則更新過后,后面的要用沒更新過的值(左上方)就找不到了。

記錄之后,求出當前數值,把記錄的temp值給了previous即可。

	public static int solution5(int nLevel, int kChess) {int bsTimes = log2N(nLevel) + 1;if (kChess >= bsTimes) {return bsTimes;}int[] dp = new int[kChess];int res = 0;while (true) {res++;//壓縮空間記得記錄次數int previous = 0;for (int i = 0; i < dp.length; i++) {int tmp = dp[i];dp[i] = dp[i] + previous + 1;previous = tmp;if (dp[i] >= nLevel) {return res;}}}}public static int log2N(int n) {int res = -1;while (n != 0) {res++;n >>>= 1;}return res;}

?

?

?

?

本題只是填空題,第一種方法就完全能算出來,就是為了追求最優解,追求思維的鍛煉。寫下了本文。

?

?

測試:

暴力:? ? ? ? ? ? ? ? ? ? ? ? O(N!)

DP:? ? ? ? ? ? ? ? ? ? ? ? ? ? O(N*N*K)? O(N*K)

壓空間:? ? ? ? ? ? ? ? ? ? O(N*N*K)? O(N)

四邊形不等式優化? ? ?O(N*N)? ? ? ?

最優:? ? ? ? ? ? ? ? ? ? ? ? ?O(K*M)? ? O(N)

		long start = System.currentTimeMillis();solution1(30, 2);long end = System.currentTimeMillis();System.out.println("cost time: " + (end - start) + " ms");start = System.currentTimeMillis();solution2(30, 2);end = System.currentTimeMillis();System.out.println("cost time: " + (end - start) + " ms");start = System.currentTimeMillis();solution3(30, 2);end = System.currentTimeMillis();System.out.println("cost time: " + (end - start) + " ms");start = System.currentTimeMillis();solution4(30, 2);end = System.currentTimeMillis();System.out.println("cost time: " + (end - start) + " ms");start = System.currentTimeMillis();solution5(30, 2);end = System.currentTimeMillis();System.out.println("cost time: " + (end - start) + " ms");
/*
結果:
cost time: 7043 ms
cost time: 0 ms
cost time: 0 ms
cost time: 0 ms
cost time: 0 ms
*/

暴力時間實在是太久了,只測一個30,2

?

后四種方法測的大一些(差點把電腦測炸了,cpu100內存100):

solution(100000, 10):

solution2 cost time: 202525 ms
solution3 cost time: 38131 ms
solution4 cost time: 11295 ms
solution5 cost time: 0 ms

?

感受最優解的強大:

solution5(1000 000 000,100):0 ms

solution5(1000 000 000,10):0 ms

最優解永遠都是0 ms,我也是服了。。

?

對比方法,在時間復雜度相同的條件下,空間復雜度一樣會影響時間,因為空間太大的話,申請空間是相當浪費時間的。并且空間太大電腦會炸,所以不要認為空間不重要。

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445468.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445468.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445468.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構課上筆記15

圖的存儲 多重鏈表&#xff1a;完全模擬圖的樣子&#xff0c;每個節點內的指針都指向該指向的節點。 節點結構內指針數為度 缺點&#xff1a;浪費空間、不容易操作 數組表示法&#xff08;鄰接矩陣表示法&#xff09; 可用兩個數組存儲。其中一個 一維數組存儲數據元素&#…

c++基礎學習(05)--(指針,引用)

文章目錄目錄1.指針2.引用目錄 1.指針 #include <iostream>using namespace std;int main () {int var1;char var2[10];cout << "var1 變量的地址&#xff1a; ";cout << &var1 << endl;cout << "var2 變量的地址&#xff…

由旅行商問題認識何為狀態壓縮

動態規劃 動態規劃(dynamic programming)是運籌學的一個分支&#xff0c;是求解決策過程(decision process)最優化的數學方法。20世紀50年代初美國數學家R.E.Bellman等人在研究多階段決策過程(multistep decision process)的優化問題時&#xff0c;提出了著名的最優化原理(pri…

c++基礎學習(06)--(時間,輸入輸出,數據結構)

文章目錄目錄1.時間2.輸入輸出數據結構目錄 1.時間 當前日期和時間 下面的實例獲取當前系統的日期和時間&#xff0c;包括本地時間和協調世界時&#xff08;UTC&#xff09;。 #include <iostream> #include <ctime>using namespace std;int main( ) {// 基于當前…

Abstract Self-Balancing Binary Search Tree

二叉搜索樹 二叉查找樹&#xff08;Binary Search Tree&#xff09;&#xff0c;&#xff08;又&#xff1a;二叉搜索樹&#xff0c;二叉排序樹&#xff09;它或者是一棵空樹&#xff0c;或者是具有下列性質的二叉樹&#xff1a; 若它的左子樹不空&#xff0c;則左子樹上所有結…

AVL Tree

前言 希望讀者 了解二叉搜索樹 了解左旋右旋基本操作 https://blog.csdn.net/hebtu666/article/details/84992363 直觀感受直接到文章底部&#xff0c;有正確的調整策略動畫&#xff0c;自行操作。 二叉搜索樹 二叉查找樹&#xff08;Binary Search Tree&#xff09;&a…

c++基礎學習(07)--(類)

文章目錄目錄類與對象1.類成員函數2.類訪問修飾符3.構造函數與析構函數4.拷貝構造函數5. 友元函數6.內聯函數7.this指針8.指向類的指針9.類的靜態成員目錄 類與對象 #include <iostream>using namespace std;class Box {public:double length; // 長度double breadth;…

【大總結1】數據結構與傳統算法總結

由于時間和水平有限&#xff0c;肯定有錯誤或者寫得不好的地方 歡迎在文章下評論指出。 涉及語言&#xff1a; py3&#xff1a;注重算法本身的知識 c/c&#xff1a;實現基礎數據結構和算法 java&#xff1a;實現較復雜數據結構 一、概述 c語言知識體系 算法體系參考 課上筆…

c++基礎學習(08)--(繼承、重載、多態、虛函數)

文章目錄目錄1.繼承2.重載3.多態 && 虛函數目錄 1.繼承 #include <iostream>using namespace std;// 基類 class Shape {public:void setWidth(int w){width w;}void setHeight(int h){height h;}protected:int width;int height; };// 派生類 class Rectang…

圖的應用

1. 圖的應用總覽 在數據結構中圖的應用很廣泛&#xff0c;本文主要從以下四個方面介紹&#xff1a; ①最小生成樹&#xff1a;給定一個無向網絡&#xff0c;在該網的所有生成樹中&#xff0c;使得各邊權數之和最小的那棵生成樹稱為該網的最小生成樹&#xff0c;也叫最小代價…

c++基礎學習(09)--(數據抽象、數據封裝、接口)

文章目錄目錄1.數據抽象2.數據封裝3.抽象接口類目錄 1.數據抽象 數據抽象&#xff1a;就是把它當做黑箱子使用&#xff0c;內部實現與外部接口分開 C類實現數據抽象&#xff0c;如sort()函數&#xff0c;ostream的cout對象 #include <iostream> using namespace std…

吃豆人游戲開發

這個文章怎么突然這么多人看啊。請大家多關注我其他文章啊 html&#xff1a; <html> <head> <meta charset"utf8"> <title>html5 pacman吃豆人游戲代碼</title><style>body{background-color: #292929}*{padding:0;margin:0;}.w…

c++基礎學習(10)--(文件、流、異常處理、動態內存、命名空間)

文章目錄目錄1.文件和流2.異常處理3.動態內存4.命名空間目錄 1.文件和流 注意 文件打開方式中的in和out都是相對于內存&#xff08;計算機&#xff09;而言的&#xff0c;計算機讀取文件&#xff0c;是將數據從磁盤中的文件讀入到內存中&#xff0c;所以用的是in #include &…

數據結構和算法(02)---字符串(c++)

文章目錄目錄一.c風格的字符串與操作函數1.c風格字符串2.c風格字符串處理函數二.c中的字符串與操作函數1.c中的string類2.string類的基本操作3.string類的操作匯總目錄 數據結構&#xff1a; 邏輯結構&#xff1a;數組&#xff0c;棧&#xff0c;隊列&#xff0c;字符串&#x…

如何學習數據結構和算法——大佬文章匯總

第一篇 第二篇、 作者&#xff1a;左程云 我分別說一下國內和國外的行情。 國內的話&#xff0c;一般來講&#xff0c;工資高的公司在面試時算法和數據結構題目的比重較大&#xff0c;工資一般的公司比重較小。當然同樣公司的不同崗位&#xff0c;要求也會不同&#xff0c;…

c++基礎學習(13)--(STL、標準庫)

文章目錄目錄1. STL教程2.標準庫3.有用的資源目錄 1. STL教程 #include <iostream> #include <vector> using namespace std;int main() {// 創建一個向量存儲 intvector<int> vec; int i;// 顯示 vec 的原始大小cout << "vector size " &…

哈夫曼實現文件壓縮解壓縮(c語言)

寫一個對文件進行壓縮和解壓縮的程序&#xff0c;功能如下&#xff1a; ① 可以對純英文文檔實現壓縮和解壓&#xff1b; ② 較好的界面程序運行的說明。 介紹哈夫曼&#xff1a; 效率最高的判別樹即為哈夫曼樹 在計算機數據處理中&#xff0c;霍夫曼編碼使用變長編碼表對源…

c++基礎學習(11)--(模板、預處理器、信號處理)

文章目錄目錄1.模板2.預處理器3.信號處理目錄 1.模板 模板是泛型編程的基礎&#xff0c;泛型編程&#xff1a;以一種獨立于任何特定類型的方式 #include <iostream> #include <string>using namespace std;template <typename T> inline T const& Max…

java 面向對象必懂概述

&#xff08;這是大體框架&#xff0c;以后部分知識細寫&#xff09; 1 抽象過程 所有編程語言都提供抽象機制。可以認為&#xff0c;人們能解決的問題的復雜性直接取決于抽象的類型和質量。 類型&#xff1a;指的是所抽象的是什么。 匯編語言是對底層機器語言的輕微抽象…

c++基礎學習(12)--(多線程、Web編程)

文章目錄目錄1.多線程2.web編程目錄 1.多線程 #include <iostream> // 必須的頭文件 #include <pthread.h>using namespace std;#define NUM_THREADS 5// 線程的運行函數 void* say_hello(void* args) {cout << "Hello Runoob&#xff01;" <&…