POJ 3181 Dollar Dayz

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

你可能感兴趣的:(dp,ACM,ICPC)