定义状态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;
}