POJ 3181题目大意如下:
给出一个正整数N,并指定只能在1到K的范围内,使用这K中整数将N分解,问分解的方式有多少种。
如果按一般的方法来想,我们可以这样定义表达式:dp[i][j]表示使用前i种数来组成j的方法数,那么就有dp[i + 1][j] = Sigma(0, min{j, n(i + 1)})dp[i][j - k]。
但是这样算的话,就会是这样:
for (int i = 1; i < N; i++) {
for (int j = 1; j <= K; j++) {
for (int k = 0; k <= j; k += i + 1)
dp[i + 1][j] += dp[i][j - k];
}
}
三重循环,时间上的消耗有点多, 而且在实验1000, 100的时候,出现溢出。换一种思考方式,重新定义dp的含义:dp[i][j]表示整数i可以分解成最大数为j的组合。那么它的表达式可以写成:
dp[i][j] = dp[i][j - 1] + dp[i - j][j];
这个式子就如字面上的那么好理解,然而由于出现溢出,所以可以将dp的高32和第32份离,采用dp_H和dp_L分别储存;
dp_H[i][j] = dp_H[i][j - 1] + dp_H[i - j][j] + (dp_L[i][j - 1] + dp_L[i - j][j]) / INF;
dp_L[i][j] = (dp_L[i][j - 1] + dp_L[i - j][j]) % INF;
(dp_H为高位,要考虑低位的进位,dp_L为低位,要考虑的是高位加法的低位部分)
同时还发现上面这个式子可以进一步化简,因为dp_H[i][j]是用前一个dp_H[i][j]求解的,所以可以将那一维优化不用,通过不断覆盖dp就可以实现(当然外围得是j控制循环),代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const long long int INF = 1e18;
long long int dp_L[1005], dp_H[1005];
int N, K;
void solve() {
memset(dp_L, 0, sizeof(dp_L));
memset(dp_H, 0, sizeof(dp_H));
dp_L[0] = 1;
for (int j = 1; j <= K; j++) {
for (int i = 1; i <= N; i++) {
if (j <= i) {
dp_H[i] = dp_H[i] + dp_H[i - j] + (dp_L[i] + dp_L[i - j]) / INF;
dp_L[i] = (dp_L[i] + dp_L[i - j]) % INF;
}
}
}
if (dp_H[N])printf("%lld", dp_H[N]);
printf("%lld\n", dp_L[N]);
}
int main(int argc, const char * argv[]) {
// insert code here...
scanf("%d %d", &N, &K);
solve();
return 0;
}
Accept 708K / 47MS