1. 程式人生 > >[NYOJ176] 整數劃分(二) [遞推計數][整數劃分]

[NYOJ176] 整數劃分(二) [遞推計數][整數劃分]

[ L i n k \frak{Link} ]


經典dp,整數劃分。
套路的想法是把複雜的問題劃分為多個1相加。
對於這道題考慮到如果劃分出來的某個數是1,那麼去掉這個1,這個數就沒了。
所以把問題拆分之後,劃分出一個新的數的條件就是多分出一個一。

計數問題,想要計所有情況的數一般不難,難的是去重。
這道問題我們現在只需要考慮取了哪些值,而不能多關心其它的,否則就會有重複狀態。
在轉移的時候,我們顯然不需要也不能關心之前劃分具體是怎麼樣的:
我們只需要考慮之前用掉了多少值,劃分出了幾個數就可以了。
狀態就有了。 ( i , j )

\mathcal{(i,j)}
按照拆分問題的思路,一個新的數出現,那麼它的初始值應該是一。
至於要讓現在這些數的值增加,我們顯然不好“從裡面找一個”加一。
因為這樣的話就是關心劃分的具體情況了。這不好。
所以我們就得把前面的看作整體,全部加上一。
隨著前面這些數出現時間可重集的不同,我們剛好能夠統計出
( i
, j ) \mathcal{(i,j)}
時的劃分方案數 F ( i , j ) \mathcal{F(i,j)}

實現的時候用填表法或者刷表法都不會重複計數。


填表
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int T, n, m;
int F[105][105];
int main() {
	scanf("%d", &T);
	while (T--) {
		memset(F, 0, sizeof(F));
		scanf("%d%d", &m, &n);
		F[0][0] = 1;
		for (int i = 1; i <= m; ++i) {
			for (int j = 1; j <= i; ++j) {
				F[i][j] = F[i-1][j-1] + F[i-j][j];
			}
		}
		printf("%d\n", F[m][n]);
	}
	return 0;
}
刷表
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int T, n, m;
int F[105][105];
int main() {
	scanf("%d", &T);
	while (T--) {
		memset(F, 0, sizeof(F));
		scanf("%d%d", &m, &n);
		F[0][0] = 1;
		for (int i = 0; i <= m; ++i) {
			for (int j = 0; j <= i; ++j) {
				if (i+1 <= m && j+1 <= n) F[i+1][j+1] += F[i][j];
				if (i+j <= m) F[i+j][j] += F[i][j];
			}
		}
		printf("%d\n", F[m][n]);
	}
	return 0;
}