程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 一道筆試題:撈魚問題

一道筆試題:撈魚問題

編輯:C++入門知識

題目:20個桶,每個桶中有10條魚,用網從每個桶中抓魚,每次可以抓住的條數隨機,每個桶只能抓一次,問一共抓到180條的排列有多少種 (也可求概率)。           分析一下        這道題其實可以這樣理解,假設我已經抓到了180條魚,而這180條魚來自20個桶中,反過來就是我們將這180條魚放到20個桶中且要保證每個桶的魚在(0-10)之間,有多少種方法。假設我們在前i個桶中抓取了k(0<=k<=10*i)條魚,那麼抓取180條魚的排列種數就等於在剩下的(20-i)個桶中抓取(180-k)條魚的方法加上前i個桶中抓取k條魚的方法。       再想一下        由於總共有200條魚,我們抓走了180條魚,那麼還剩下多少條呢?顯然是20條。再用上面的思維,我們就可以這樣想,將20條魚放回這20個桶中滿足每個桶中的魚(0-10),有多少種放法。這裡當然是因此了一個排列問題而不是組合問題,因為我第一個桶放1條魚、第2個桶不放與第一個桶不放、第二個桶放1條魚,是兩種的不同的放法。        這樣分析下來,感覺就是一個跳台階問題:一個梯子有20步,每次可以跳0-10步,20步跳完多少中跳法(可選擇不跳)。         編程思想       思想其實跟完全背包問題類似,我們首先考慮第20個桶放多少魚,那麼我們就可以減去最後一個桶放的魚數,再去考慮前面19個桶怎麼放剩下的魚。這樣形成了一個遞歸,我們就可以很快寫出如下代碼。   [cpp]   //bucket,表示當前考慮到第幾個桶,當然首先考慮第20個桶   //fishN,表示當前有多少魚   //返回值表示有多少中方法   int Func(int bucketN,int fishN)   {       if(bucketN<0 || fishN<0)return 0;//錯誤處理          if(bucketN == 1){           if(fishN>=0 && fishN<=10)//第1個桶裡面放0-10的魚,方法只有一種               return 1;           else{               return 0;  //其他用0種方法           }       }          int sum = 0;          for(int i=0; i<=10; ++i){           sum += Func(bucketN-1,fishN-i);//考慮前面bucket-1的組合,同時減掉當前放的魚       }       return sum;   }     遞歸優化      上面的代碼,其實有很多重復計算。下面我們簡單的分析一下:   F(20,20) = F(19,19)+ F(19,18).......; //考慮第20個桶放一條魚和兩條魚的情況   F(19,19) = F(18,17)+.....;//第19個桶放2條魚  ①   F(19,18) = F(18,17)+.....;//第19個桶放1條魚  ②        我們發現①和②都調用了F(18,17),但是它們確實各算各的,這樣就存在著很多類似的重復計算。該遞歸樹,基本全是重復的點,這樣時間復雜度被拖得很高。那麼,我們只要設計一個數組來把計算好的值保存下來,這樣避免了很多重復的計算。代碼如下:   [cpp]   //bucket,表示當前考慮到第幾個桶,當然首先考慮第20個桶   //fishN,表示當前有多少魚   //返回值表示有多少中方法   int dp[21][200] = {0};   int FuncOptimize(int bucketN,int fishN)   {       if(bucketN<0 || fishN<0)return 0;          if(bucketN == 1){           if(fishN>=0 && fishN<=10)               return 1;           else{               return 0;           }       }       if(dp[bucketN][fishN] == 0){ //這個子過程沒有被計算我們采取調用遞歸計算           for(int i=0; i<=10; ++i){               dp[bucketN][fishN] += FuncOptimize(bucketN-1,fishN-i);           }       }              return dp[bucketN][fishN];   }     下面我們用我們熟悉的斐波拉契序列做一個實驗,我們同樣的優化方式來做,看下程序跑得快了多少?   [cpp]   #include<iostream>   #include <windows.h>   using namespace std;   long long fibonacci[100];   long long Fibonacci(int n)   {       if(n == 0)return 1;       if(n == 1)return 1;          return Fibonacci(n-1) + Fibonacci(n-2);   }      long long FibonacciOptimize(int n)   {       if(n == 0)return 1;       if(n == 1)return 1;          if(fibonacci[n] == 0){           fibonacci[n] = FibonacciOptimize(n-1) + FibonacciOptimize(n-2);       }          return fibonacci[n];   }         int main()   {       DWORD seconds = GetTickCount();       cout<<Fibonacci(40)<<endl;       cout<<"優化之前:"<<(GetTickCount() - seconds)<<"ms"<<endl;       seconds = GetTickCount();       cout<<FibonacciOptimize(40)<<endl;       cout<<"優化之後:"<<(GetTickCount() - seconds)<<"ms"<<endl;       system("pause");       return 0;   }       這個結果很是滿意啊。     動態規劃實現        我們知道這種自頂向下的遞歸,都可以轉換為動態規劃自底向上的思想。就是我們遞歸的值是有下面的值一步步壘起來的,那麼我只需要一開始就把下面的值算好,保持在那裡,然後再算上面的時候,直接拿過來用,這就很快了。據我了解,背包問題的編程思路跟這道題是一摸一樣,只不過背包走到第i種物品的時候,第i種物品的重量已經固定,而我們這道題是第i個桶取值是一個范圍,那我們只要把這裡范圍都掃一遍就好了。   [cpp]  #include<iostream>   #include <windows.h>   using namespace std;      int dp[21][200] = {0};   int FuncDp(int bucketN,int fishN)   {       int i,j,k;          for(i=0; i<=10; ++i)           dp[1][i] = 1;          for(i=2; i<=bucketN; ++i){           for(j=0; j<=fishN; ++j){               for(k=0; k<=10&&j-k>=0; ++k){//可以取0-10                   dp[i][j] += dp[i-1][j-k];               }           }       }          return dp[bucketN][fishN];   }      int main()   {       cout<<FuncDp(20,180)<<endl;       memset(dp,0,sizeof(dp));       cout<<FuncDp(20,20)<<endl;       system("pause");       return 0;   }               其實,代碼還可以更簡練,仔細想想,就是初始化狀態的方法;其實初始化合法狀態完全可以這樣想,問題始終都是分解成子問題的,根據遞歸的實現方法,只有分解到0個桶裝0條魚才是合法的,那麼我們就初始化這一個狀態為合法即可,然後從第一個桶開始向上計算,代碼如下:   [cpp]  #include <iostream>      using namespace std;      int dp[21][200];   int i, j, k;       void main()   {       int bucketN, fishN;       scanf("%d %d", &bucketN, &fishN);           dp[0][0] = 1;  /* 初始化合法狀態 */           for(int i = 1; i <= bucketN; ++i)  /* 從第一個桶開始 */       {           for(int j = 0; j <= fishN; ++j)           {               for(int k = 0; k <= 10 && j-k >= 0; ++k)               {                   dp[i][j] += dp[i-1][j-k];               }           }       }       printf("%d\n",dp[bucketN][fishN]);   }     想不通我的空間優化怎麼不成功,類似背包問題的優化,555555555555555555。有大神可以幫忙解釋一下啊?   [cpp]   #include<iostream>   #include <windows.h>   using namespace std;      int dp[200] = {0};   int FuncDp(int bucketN,int fishN)   {       int i,j,k;          for(i=0; i<=10; ++i)           dp[i] = 1;          for(i=2; i<=bucketN; ++i){           for(j=fishN; j>=0; --j){//反著遍歷,防止覆蓋               for(k=0; k<=10&&j-k>=0; ++k){//可以取0-10                   dp[j] += dp[j-k];               }           }       }          return dp[fishN];   }      int main()   {       cout<<FuncDp(20,180)<<endl;       memset(dp,0,sizeof(dp));       cout<<FuncDp(20,20)<<endl;       system("pause");       return 0;   }    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved