HDU 4734 F(x) 数位dp

来源:互联网 发布:软件注册授权系统 编辑:程序博客网 时间:2024/06/04 19:48

定义状态dp[i][j][m]表示以j开头的i位十进制数字中,函数值小于等于m的数字的个数。

有如下状态转移方程


代码如下:

#include <algorithm>#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>using namespace std;int A, B, bit[15];int dp[15][15][5005];int digit[15], len;void init(){    memset(dp, 0, sizeof(dp));    for (int i = 0; i <= 9; i++)        for (int j = i; j <= 5000; j++)            dp[1][i][j] = 1;    for (int i = 2; i <= 10; i++)        for (int j = 0; j <= 9; j++)            for (int m = 0; m <= 5000; m++)                for (int k = 0; k <= 9; k++)                    if (m >= j * bit[i])                        dp[i][j][m] += dp[i - 1][k][m - j * bit[i]];}int cal(int n){    memset(digit, 0, sizeof(digit));    int len = 0;    while (n)    {        digit[++len] = n % 10;        n /= 10;    }    int ans = 0, pre = 0;    for (int i = len; i >= 1; i--)    {        for (int j = 0; j < digit[i]; j++)            ans += dp[i][j][A - pre];        pre += digit[i] * bit[i];        if (pre > A)            break;    }    return ans;}int main(){    //freopen("test.txt", "r", stdin);    int T, Case = 1;    scanf("%d", &T);    bit[1] = 1;    for (int i = 2; i <= 10; i++)        bit[i] = bit[i - 1] * 2;    init();    while (T--)    {        scanf("%d%d", &A, &B);        int temp[15], len = 0;        while (A)        {            temp[++len] = A % 10;            A /= 10;        }        for (int i = 1; i <= len; i++)            A += temp[i] * bit[i];        printf("Case #%d: ", Case++);        printf("%d\n", cal(B + 1));    }    return 0;}

通过写这道题目,我发现dfs写法虽然看起来简洁,可是要考虑的东西要更多。

一开始我是每次都dfs一遍,最后超时。看了大牛的博客才发现只要把状态稍稍改一下,dp数组就可以只计算一次。

具体是这样的,如果dp[pos][sum]中的sum表示的是前缀和的话,dfs的临界条件应该是return sum <= A。通过这个临界条件很容易知道这种dp是和输入的A有直接关系的,所以每次dfs都必须将dp数组memset,这样一来无法使用上一次dp的信息。

但是如果dp[pos][sum]中的sum表示的是最多还能凑的数,也就是F(A) - prefix(前缀)。dfs的临界条件就变成了 return sum >= 0。通过这个临界条件很容易知道这种dp是和输入的A无关的,我们只需要在第一次dp的时候memset即可,以后的dp可以直接使用以前的数据。

代码如下:

#include <algorithm>#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>using namespace std;int dp[15][5005];int digit[10], bit[15];int A, B;int dfs(int pos, int val, int limit){    if (!pos)        return val >= 0;    if (val < 0)        return 0;    if (!limit && dp[pos][val] != -1)        return dp[pos][val];    int up = limit ? digit[pos] : 9;    int ans = 0;    for (int i = 0; i <= up; i++)        ans += dfs(pos - 1, val - i * bit[pos], limit && i == up);    return limit ? ans : dp[pos][val] = ans;}int cal(int n){    int len = 0;    while (n)    {        digit[++len] = n % 10;        n /= 10;    }    return dfs(len, A, 1);}int main(){//freopen("test.txt", "r", stdin);    bit[1] = 1;    for (int i = 2; i <= 10; i++)        bit[i] = bit[i - 1] * 2;    int T, Case = 1;    scanf("%d", &T);    memset(dp, -1, sizeof(dp));    while (T--)    {        scanf("%d%d", &A, &B);        int temp[15], len = 0;        while (A)        {            temp[++len] = A % 10;            A /= 10;        }        A = 0;        for (int i = len; i >= 1; i--)            A = A * 2 + temp[i];        printf("Case #%d: ", Case++);        printf("%d\n", cal(B));    }return 0;}