整數n劃分成k個不為0的部分
阿新 • • 發佈:2020-07-06
題目:洛谷P1025
解法:
看到這個題目,我第一反應是組合數學的知識,題目等價於將n個相同的物品(數字1)分成k堆,求方案數。
這道題的解法在高中的排列組合肯定用到過,但是想不起來了。
方法如下:
首先,要保證計算過程中相同的方案(每堆的數量相同,但順序不同)只能計數1次,我採用的是在前一堆的基礎上增加後一堆的數量,保證後一堆的數量不小於前一堆,這樣每堆的數量是遞增的,也就不會擔心相同數量的堆順序不同而重複計數了。
那麼,怎樣保證後一堆的數量不小於前一堆呢?可以直接考慮後一堆的數量=前一堆的數量+i(i>=0),這樣迭代的話需要k-1個迴圈,感覺不太好做,放棄了。
之後,我採用了少量多次,每次分配的堆數不超過上一次的辦法確保後一堆數量不小於前一堆。大體思路是,先分配給k個堆相同的數量i,再分配給h個堆(h<k)相同的數量j(j>=0),令k=h,h更新,繼續進行。一直進行下去,直到可分配的數量變為0或者只剩1堆可以分配,那麼一種分配方案就產生了。
可以看出,這是一種遞迴的演算法。
每一次操作的h、j不同,產生的解決方案也就不同。
而可以發現,當j>1時,分給h個堆的相同數量j可以分成分給h個堆的相同數量1,分j次。取j>1會使我們漏掉一些分配方案。而取j=0相當於什麼都沒幹。
所以我們將,下次分配的數量定為1,當下次分配的數量j確定了,變數也就剩下次分配的堆數h。
分配的堆數h滿足1<=h<=k,要保證下次分配的堆的數量小於當前堆的數量,那麼我們令h=k-下次不打算分配的堆數i(0<=i<k)
大體上的思路搞定了,但是題目要求每堆不為空,而如果第一次分配的數目就小於k,那麼是不符合題意的。所以,現將k堆分配數量1,可分配的數量變成n=n-k,堆數k=k,之後再遞迴地進行上述的過程。
程式碼如下:
#include <stdio.h> long long int sum = 0;//方案數 void div(int n, int k)//n,k代表可分配的數量和分配的堆數 { if (k < 1 || n < 0)//計算過程中,可能存在這種情況,這樣的分配方案是不可行的 return; if (k == 1 || n == 0)//當只剩下一個可以分配的堆或者可分配數量都已經分配完了,代表一種方案產生 { sum++; return; } for (int i = 0; i < k; i++)//i代表下次不分配的堆個數,對不同的i值進行遞迴 div(n - (k-i), k - i); } int main() { int n = 0, k = 0; scanf("%d", &n); scanf("%d", &k); div(n - k, k);//k堆都分數量1,確保k堆都不為0 printf("%d", sum); return 0; }