一、這個專題有什么用
練練DP
練練組合數學
......
二、正題
此類問題有如下幾種形態:
1. 將n划分成若干正整數之和的划分數。
2. 將n划分成k個正整數之和的划分數。
3. 將n划分成最大數不超過k的划分數。
4. 將n划分成若干奇正整數之和的划分數。
5. 將n划分成若干不同整數之和的划分數。
1:將n划分成若干正整數的划分數
(1):划分數可以存在相同的數
那么,設dp[n][m]表示整數 n 的划分中,每個數不大於 m 的划分數。
則划分數可以分為兩種情況:
a.划分中每個數都小於 m,相當於每個數不大於 m- 1, 故划分數為 dp[n][m-1]
b.划分中有一個數為 m. 那就在 n中減去 m ,剩下的就相當於把 n-m 進行划分, 故划分數為 dp[n-m][m]
總遞推式:dp[n][m]=dp[n][m-1]+dp[n-m][m]
(2):划分數不可以存在相同的數
若還是設成同樣的狀態,從(1)可以看出,a條件是不會改變的,而b條件中,n-m后由於不存在重復,則划分數變為dp[n-m][m-1]
總遞推式:dp[n][m]=dp[n][m-1]+dp[n-m][m-1]
2:將n划分成k個正整數的划分數
設dp[i][k]為,把i分成k分的划分法
那么也有兩種情況:
a.n份中不包括1的情況,那么每份中都可以拿出1來,即為dp[i-k][k]
b.n份中起碼有1個1的情況,那么把這個1去掉,變為dp[i-1][k-1]
總遞推式:dp[i][k]=dp[i-k][k]+dp[i-1][k-1]
3:將n划分成若干奇數的划分數
設g[i][j]:將i划分成j個偶數
f[i][j]:將i划分成j個奇數
對於偶數來講,為f[i-j][j]中的共j個數,每個加1,則變成偶數。
對於奇數來講,為g[i-j][j]中的共j個數,每個加1,則變成奇數;且還有新加入的1,則為f[i-1][j-1]
所以遞推式為:g[i][j]=f[i-j][j] , f[i][j]=f[i-1][j-1]
可以看一看HitOJ1402的代碼:
1 /* 2 * hit1402.c 3 * 4 * Created on: 2011-10-11 5 * Author: bjfuwangzhu 6 */ 7 8 #include<stdio.h> 9 #include<string.h> 10 #define nmax 51 11 int num[nmax][nmax]; //將i划分為不大於j的個數 12 int num1[nmax][nmax]; //將i划分為不大於j的不同的數 13 int num2[nmax][nmax]; //將i划分為j個數 14 int f[nmax][nmax]; //將i划分為j個奇數 15 int g[nmax][nmax]; //將i划分為j個偶數 16 void init() { 17 int i, j; 18 for (i = 0; i < nmax; i++) { 19 num[i][0] = 0, num[0][i] = 0, num1[i][0] = 0, num1[0][i] = 0, num2[i][0] = 20 0, num2[0][i] = 0; 21 } 22 for (i = 1; i < nmax; i++) { 23 for (j = 1; j < nmax; j++) { 24 if (i < j) { 25 num[i][j] = num[i][i]; 26 num1[i][j] = num1[i][i]; 27 num2[i][j] = 0; 28 } else if (i == j) { 29 num[i][j] = num[i][j - 1] + 1; 30 num1[i][j] = num1[i][j - 1] + 1; 31 num2[i][j] = 1; 32 33 } else { 34 num[i][j] = num[i][j - 1] + num[i - j][j]; 35 num1[i][j] = num1[i][j - 1] + num1[i - j][j - 1]; 36 num2[i][j] = num2[i - 1][j - 1] + num2[i - j][j]; 37 } 38 } 39 } 40 f[0][0] = 1, g[0][0] = 1; 41 for (i = 1; i < nmax; i++) { 42 for (j = 1; j <= i; j++) { 43 g[i][j] = f[i - j][j]; 44 f[i][j] = f[i - 1][j - 1] + g[i - j][j]; 45 } 46 } 47 } 48 int main() { 49 #ifndef ONLINE_JUDGE 50 freopen("data.in", "r", stdin); 51 #endif 52 int n, k, i, res0, res1, res2, res3, res4; 53 init(); 54 while (~scanf("%d %d", &n, &k)) { 55 res0 = num[n][n]; 56 res1 = num2[n][k]; 57 res2 = num[n][k]; 58 for (i = 0, res3 = 0; i <= n; i++) { 59 res3 += f[n][i]; 60 } 61 res4 = num1[n][n]; 62 printf("%d\n%d\n%d\n%d\n%d\n\n", res0, res1, res2, res3, res4); 63 } 64 return 0; 65 }
4.將正整數划分成連續的正整數之和
eg:15可以划分成4種連續整數相加的形式
15
7 8
4 5 6
1 2 3 4 5
首先考慮一般形式,設n為被划分的正整數,x為划分后的最小整數,如果n有一種m划分,那么數列為
x,x+1,x+2....x+m-1
而其結果的公式為( x * i + i * ( i + 1 ) / 2 )
滿足條件的划分就是使x為正整數的所有情況。
如上例,當i = 1時,即划分成一個正整數時,x = 15, 當i = 2時, x = 7。
當x = 3時,x = 4, 當x = 4時,4/9,不是正整數,因此,15不可能划分成4個正整數相加。
當x = 5時,x = 1。
這里還有一個問題,這個i的最大值是多少?
有一點可以肯定,它一定比n小。我們可以做一個假設,
假設n可以拆成最小值為1的划分,如上例中的1 2 3 4 5。這是n的最大數目的划分。如果不滿足這個假設,
那么 i 一定比這個划分中的正整數個數小。因此可以得到這樣一個公式i * (i + 1) / 2 <= n,即當i滿足
這個公式時n才可能被划分。
代碼如下
1 void split(int n) { 2 int i, j, te, x, xlen; 3 for (i = 1, xlen = 0; (te = i * (i - 1) / 2) < n; i++) { 4 x = n - te; 5 if (x % i == 0) { 6 x /= i; 7 printf("%d", x); 8 for (j = 1; j < i; j++) { 9 printf("%d ", x + j); 10 } 11 printf("\n"); 12 xlen++; 13 } 14 } 15 printf("%d\n", xlen); 16 }
5.求划分因子乘積最大的一個划分及此乘積
問題簡述:
給定一個正整數n, 則在n所有的划分中, 求因子乘積最大的一個划分及此乘積。例如:8 = {8}, {7, 1}, {6, 2}, {5, 3}, {4, 4}, {3, 3, 2}, {2, 2, 2, 2} 等,那么在這些當中,3 * 3 * 2 的乘積最大,所以輸出整個划分和這個乘積 18。
算法分析:
這是我在某個論壇上看到的問題,以及別人針對此問題的數學分析,現簡單的整理如下:
(1)對於任意大於等於4的正整數m, 存在一個划分m = m1+m2, 使 m1*m2 >= m
證: 令m1 = int(m/2), 則 m1 >= 2 , m2 = m-m1; 那么m2 > 2,並且 m2 >= m/2 >= m1; m1*m2 >= 2*m2 >= m; 證畢;
該證明簡單的來說就是:對於一個大於等於4的正整數m,存在一個2塊划分的因子,這兩個因子的乘積總是不小於原數m本身。
(2)由(1)知此數最終可以分解為 2^r * 3^s。現證明 r <= 2;
證:若r > 2, 則至少有3個因子為2, 而2*2*2 < 3*3;
所以可以將3個為2的因子,換為兩個因子3;積更大;證畢。
綜合(1),(2),則有:任何大於4的因子都可以有更好的分解, 而4可以分解為2*2。
所以:此數應該分解為 2^k1 * 3^k2。而且可以證明 k1>=0 並且 k1 <= 2,因此:
A.當n = 3*r 時, 分解為 3^r
B.當n = 3*r+1時, 分解為 3^(r-1)*2*2
C.當n = 3*r+2時, 分解為 3^r*2
剩下編程處理,那就是太簡單了,首先是處理 <= 4的特殊情況,再對>4的情況進行模3的3種情況的判斷,最后一一輸出。
可見,數學在整數划分問題上有太強的功能。誰叫這個問題叫整數划分呢,不與數學密切才怪! ^_^。
6.從小學奧數的整數划分得到的有用結論
例1:把14分拆成若干個自然數的和,再求出這些數的積,要使得到的積最大,應該把14如何分拆?這個最大的乘積是多少?
解:我們先考慮分成哪些數時乘積才能盡可能地大。
首先,分成的數中不能有1,這是顯然的。
其次,分成的數中不能有大於4的數,否則可以將這個數再分拆成2與另外一個數的和,這兩個數的乘積一定比原數大,例如7就比它分拆成的2和5的乘積小。
再次,因為4=2×2,故我們可以只考慮將數分拆成2和3。
注意到2+2+2=6,2×2×2=8;3+3=6,3×3=9,因此分成的數中若有三個2,則不如換成兩個3,換句話說,分成的數中至多只能有兩個2,其余都是3。根據上面的討論,我們應該把14分拆成四個3與一個2之和,即14=3+3+3+3+2,這五數的積有最大值 3×3×3×3×2=162。
將上述結論推廣為一般情形便是:
把自然數S(S>1)分拆為若干個自然數的和: S=a1+a2+…+an,則當a1,a2,…,an中至多有兩個2,其余都是3時,其連乘積m=a1a2…an有最大值。
例2:把1993分拆成若干個互不相等的自然數的和,且使這些自然數的乘積最大,該乘積是多少?
解:由於把1993分拆成若干個互不相等的自然數的和的分法只有有限種,因而一定存在一種分法,使得這些自然數的乘積最大。
若1作因數,則顯然乘積不會最大。把1993分拆成若干個互不相等的自然數的和,因數個數越多,乘積越大。為了使因數個數盡可能地多,我們把1993分成2+3…+n直到和大於等於1993。
若和比1993大1,則因數個數至少減少1個,為了使乘積最大,應去掉最小的2,並將最后一個數(最大)加上1。
若和比1993大k(k≠1),則去掉等於k的那個數,便可使乘積最大。
所以n=63。因為2015-1993=22,所以應去掉22,把1993分成(2+3+…+21)+(23+24+…+63)這一形式時,
這些數的乘積最大,其積為 2×3×…×21×23×24×…×63。
以上內容轉自 qingyezhu的cnblogs 並加以改編
