题目描述

#10166. 「一本通 5.3 练习 1」数字游戏 - 题目 - LibreOJ (loj.ac) 由于科协里最近真的很流行数字游戏,某人又命名了一种取模数,这种数字必须满足各位数字之和  为 。现在大家又要玩游戏了,指定一个整数闭区间 ,问这个区间内有多少个取模数。

输入格式

题目有多组测试数据。每组只含三个数字 

输出格式

对于每个测试数据输出一行,表示各位数字和  为  的数的个数。

样例

输入

1 19 9

输出

2

数据范围与提示

对于全部数据,

思路

显然是数位DP。 之前通常定义状态为长度和最高位值, 这次既然是求总和为N的各位数字之和, 那么就定义为长度和各位数字和。 假如确定前一位为An-1, 当前位为x An-1 x … … … … … … 要满足的式子为 [An-1 + x + ( ... )] % N = 0 也就是说后面n-2位随意填的情况下, 有多少种情况满足 (...) % N = [ -(An-1 + x) ] % N

因此状态表示为f[i][j][k] 共i位, 最高位为j, 各数字和取模N的余数为k的方案数 状态计算:f[i][j][k] += f[i-1][q][(k - j)%N]

代码

const int N = 33, M = 110;
int f[N][10][M];
int m;
 
int mod(int x, int y)
{
    return (x % y + y) % y;
}
 
void init()
{
    memset(f, 0, sizeof f);
    for (int i = 0; i <= 9; i++)
        f[1][i][i % m]++;
 
    for (int i = 2; i < N; i++)
        for (int j = 0; j <= 9; j++)
            for (int k = 0; k < m; k++)
                for (int x = 0; x <= 9; x++)
                    f[i][j][k] += f[i - 1][x][mod(k - j, m)];
}
 
int dp(int n)
{
    if (!n)
        return 1;
 
    vector<int> nums;
    while (n)
        nums.push_back(n % 10), n /= 10;
 
    int res = 0;
    int last = 0; // 存上个数的总和
    for (int i = nums.size() - 1; i >= 0; i--)
    {
        int x = nums[i];
        for (int j = 0; j < x; j++)
            res += f[i + 1][j][mod(-last, m)];
 
        last += x;
 
        if (!i && last % m == 0)
            res++;
    }
 
    return res;
}
 
int main()
{
 
    int a, b;
    while (cin >> a >> b >> m)
    {
        init();
        cout << dp(b) - dp(a - 1) << endl;
    }
    return 0;
}