日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

捞鱼问题

發布時間:2024/9/30 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 捞鱼问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://www.ahathinking.com/archives/112.html

話說這道題還是三年前徑點公司來學院筆試中的一道題目,當時剛進入實驗室,師兄在帶著我們做新生培訓的時候做過這道題,最近回顧DP的一些基礎,翻找以前寫的程序,發現了這道題,就貼一下,給出兩種方法的代碼,并對比了它們在不同規模問題下的效率。

題目:20個桶,每個桶中有10條魚,用網從每個桶中抓魚,每次可以抓住的條數隨機,每個桶只能抓一次,問一共抓到180條的排列有多少種 (也可求概率)。

分析:我們要在20個桶中一共抓取180條魚,每個桶能抓到魚的條數為0-10,仔細想下,這個問題是可以分解成子問題的:假設我們在前i個桶中抓取了k(0<=k<=10*i)條魚,那么抓取180條魚的排列種數就等于在剩下的(20-i)個桶中抓取(180-k)條魚的方法加上前i個桶中抓取k條魚的方法。

例如,在第一個桶中抓取了2條魚,那么總的排列數等于在剩下19個桶中抓取178條魚的排列種數;如果在第一個桶中抓取了10條魚,那么總的排列數等于在剩下19個桶中抓取170條魚的排列數,,,依次分解該問題,總的排列數就等于所有這些排列數的總和。有點DP的感覺。

換個思維,在實現上這個題目可以有更為簡潔的方法,我們看看這個問題的對偶問題,抓取了180條魚之后,20個桶中剩下了20條魚,不同的抓取的方法就對應著這些魚在20個桶中不同的分布,于是問題轉化為將20條魚分到20個桶中有多少中不同的排列方法(這個問題當然也等價于180條魚分到20個桶中有多少種不同的方法)?其中,每個桶最多放10條,最少放0條。這樣一轉化,無論是用搜索還是DP,問題規模都縮小了很大一塊。

按照這個分析,最直接的方法就是用遞歸了,遞歸實現DP問題,自頂向下,為了防止重復計算子問題(例如求19個桶放12條魚的方法數時算了一遍子問題17個桶放10條魚的方法數,在算18個桶,17個桶時就不用再計算17個桶放10條魚的情況了),一般設置一個備忘錄,記錄已經計算過的子問題,其實這個備忘錄就是在自底向上實現DP時的狀態轉移矩陣

遞歸實現,如果桶沒了,魚還有,說明這種排列不符合要求,應該結束并返回0;如果桶還有,魚沒了,說明這種排列也不符合要求;只有在桶沒了,魚也沒了的情況下才說明20條魚恰好分放到了20個桶。根據上面分析我們知道每個桶有11種情況,代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std; /* ??撈魚:將20條魚放在20個桶中,每個桶最多可以放10條 ??求得所有的排列方法 ??DP自頂向下遞歸 備忘錄 */ int dp[21][21]; /* 備忘錄,存儲子問題的解; 表示前i個桶放j條魚的方法數 */ int allocate(int bucketN, int fishN) { ????if(bucketN == 0 && fishN == 0) ????{ ????????return 1; ????} ????if(bucketN == 0 || fishN < 0) ????{ ????????return 0; ????} ????/* 如果子問題沒有計算就計算,否則直接返回即可 */ ????if(dp[bucketN][fishN] == 0) ????{ ????????for(int i = 0; i <= 10; ++i) ????????{ ????????????dp[bucketN][fishN] += allocate(bucketN-1,fishN-i); ????????} ????} ????return dp[bucketN][fishN]; } void main() { ????int bucketN, fishN; ????while(scanf("%d %d", &bucketN, &fishN)!= EOF) ????{ ????????memset(dp,0,sizeof(dp)); ????????printf("%d\n",allocate(bucketN,fishN)); ????} }

輸出:

結果如圖,先輸入一個小數據驗證解是否正確,可以看出這個解是非常大的,最初實現的兩種情況都是等了好久都沒有出來結果,一種是沒有使用備忘錄,單純遞歸的搜索,非常非常非常慢,等了兩分鐘都沒有結果;一種是沒有求對偶問題,而是求dp[20][180]也是相當的慢。

既然可以用DP,我們通常使用自底向上的方法,下面來看看非遞歸實現的方法。自底向上就需要考慮合法狀態的初始化問題,從小規模去考慮,20個桶太大,考慮零個桶,一個桶,零個桶裝多少魚都是非法的,故就是0;一個桶裝魚,裝0-10條魚都是合法的,其余的就不合法了;?dp[i][j]:前i個桶放j條魚的方法共分為11種情況:前i-1個桶放j-k(0<=k<=10)條魚的方法總和。我們可以得到狀態方程:

1 f(i,j) = sum{ f(i-1,j-k), 0<=k<=10}

考慮到這,dp的程序就出來了,代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> using namespace std; /* ??撈魚:將20條魚放在20個桶中,每個桶最多可以放10條 ??求得所有的排列方法 ??自底向上DP f(i,j) = sum{ f(i-1,j-k), 0<=k<=10} ??該方法中測試 20個桶 180條魚,與遞歸速度做對比 */ /* 實現1 */ int dp[21][200]; /* 前i個桶放j條魚的方法數 */ int i, j, k; void main() { ????int bucketN, fishN; ????while(scanf("%d %d", &bucketN, &fishN)!= EOF) ????{ ????????memset(dp,0,sizeof(dp)); ????????for(int i = 0; i <= 10; ++i)? /* 初始化合法狀態 */ ????????{ ????????????dp[1][i] = 1; ????????} ????????for(int i = 2; 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]); ????} }

輸出:

當我們測試20個桶放180條魚的方法,結果立即就算出來了,而用遞歸則是等了半天沒反應,由此我們可以看出效率的差別有多大。同時,兩個對偶問題的答案是一樣的,說明我們的分析是沒錯的,:-)。

其實,代碼還可以更簡練,仔細想想,就是初始化狀態的方法;其實初始化合法狀態完全可以這樣想,問題始終都是分解成子問題的,根據遞歸的實現方法,只有分解到0個桶裝0條魚才是合法的,那么我們就初始化這一個狀態為合法即可,然后從第一個桶開始向上計算,代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /* 實現2 */ 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]); }

從遞歸到非遞歸再到現在,一個看似規模很大很復雜的問題只用簡單的幾行代碼就可以解決,關鍵在于怎么思考,要好好修煉。


我自己用的是多重背包,但是總是計算錯誤,不知道什么原因


int f[21] = {0};memset(f,0,sizeof(f));f[0] = 1;int bucket = 2;//每個桶里有10條魚int fish = 5;//從中抓fish條魚int numPerBucket = 4;for (int i = 0 ; i < bucket; ++i ) {int c = 1;int count = numPerBucket;while (c < count) {for (int j = fish; j >= c; --j) {f[j] = f[j]+f[j - c];}count -= c;c *=2;}for (int j = fish; j >= count; --j)f[j] =f[j]+f[j - count];}cout<<f[fish];

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的捞鱼问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。