【题目描述】

若一个正整数的数字和被3整除,那么这个数也被3整除(反之亦然)。 例如,3072被3整除,并且其数字和12也被3整除。这一性质对于模9也成立。

在这个问题中,我们将研究模其他正整数下的这个性质。

给定A,B,K,输出[A,B]内满足它和它的各位数字和同时被K整除的正整数个数。

数据范围 1 <= A, B <= 2^31且0 < K < 10000

【题目链接】

COGS 1475 数字和与倍数

【解题思路】

数位DP。

第一步预处理。

设$ f(d, m_1, m_2) $表示共$ d $位,其中各数字之和对$ K $的模为$ m_1 $,组成的数字对$ K $的模为$ m_2 $,满足上述条件的数字个数。

枚一下最高位即可转移: $$ f(d, m_1, m_2) = \sum_{x = 0}^9f(d - 1, (m_1 - x) \% K,(m_2 - x10^{d - 1}) \% K ) $$

第二步统计答案。

常见思路,$ [a, b] $的答案等于$ [0, b] $的答案减去$ [0,a) $的答案。

对于$ [0, x) $的答案,我们考虑一个小于$ x $的数会有怎样的特征,要么长度小于$ x $,要么与$ x $长度相等,有一定的公共前缀,但在下一位小于$ x $。

长度小于$ x $的可以直接计入答案。

而长度和$ x $相等的数可以依据公共前缀分成若干段,每一段都可以用一个固定前缀和任取后缀表示。

例如,[0, 3212)的数可以表示为:

  • 0 ~ 999 : * * *
  • 1000 ~ 1999 : 1 * * *
  • 2000 ~ 2999 : 2 * * *
  • 3000 ~ 3211
    • 3000 ~ 3099: 30 * *
    • 3100 ~ 3199: 31 * *
    • 3200 ~ 3211
      • 3200 ~ 3209: 320 *
      • 3210 ~ 3211
        • 3210
        • 3211

(强行嵌套列表来表示树结构的也是够了...你们意会一下就好...)

而对于每个模板的方案数,都有相应的$ f(d, m_1, m_2) $与之对应。

为什么这样说呢,依蓝书上举个例子, 在模7意义下,模板22***对应$ f(3, 1, 1) $,因为:

  • 有三个星号。
  • 2 + 2 = 4,所以剩下的三位数字之和需模7为3,加起来才能被7整除。
  • 22000 模7为6,因此剩下的三位数需模7为1,加起来才能被7整除。

还是很好理解的吧~就是用星号部分去凑整。

此题还有一个坑点,那就是$ K $的范围高达10000,而$ m_1 $,$ m_2 $都是与$ K $同范围的,所以......

然而并不是这样,因为数字在2^31范围,所以数字之和的实际范围很小(大概在82左右?),所以如果$ K $超过这个范围肯定无解,直接返回零就行咯。

【AC代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
 
const int MAXK = 100;
const int MAXDIGIT = 10;
 
int k, a, b;
 
int f[MAXDIGIT + 1][MAXK][MAXK];
int pow[MAXDIGIT + 1];
 
inline int mod(int x){
    return (x % k + k) % k;
}
 
inline int dp(int d, int x, int y){
    int &ans = f[d][x][y];
 
    if(ans != -1) return ans;
 
    if(d == 0) ans = !x && !y;
    else{
        ans = 0;
        for(int i = 0; i < 10; i++){
            ans = (ans + dp(d - 1, mod(x - i), mod(y - pow[d - 1] * i)));
        }
    }
 
    return ans;
}
 
inline int split(int x, int *d){
    int n = 0;
 
    while(x) d[n++] = x % 10, x /= 10;
 
    for(int i = 0; i < n >> 1; i++) std::swap(d[i], d[n - i - 1]);
 
    return n;
}
 
inline int calc(int limit){
    int d[MAXDIGIT];
    int n = split(limit, d);
 
    int ans = 0;
    int sumDigit = 0, sum = 0;
 
    for(int i = 0; i < n; i++){
        int x = n - i - 1;
        for(int z = 0; z < d[i]; z++){
            ans += dp(x, mod(k - (sumDigit + z)), mod(k - (sum + z * pow[x])));
        }
        sumDigit = (sumDigit + d[i]) % k;
        sum = (sum + d[i] * pow[x]) % k;
    }
 
    return ans;
}
 
inline int solve(){
    if(k > MAXK) return 0;
    else return calc(b + 1) - calc(a);
}
 
inline void initPow(){
    pow[0] = 1;
    for(int i = 1; i <= MAXDIGIT; i++) pow[i] = pow[i - 1] * 10;
}
 
int main(){
    int T;
    scanf("%d", &T);
 
    initPow();
    
    for(int i = 0; i < T ; i++){
        memset(f, -1, sizeof f);
        scanf("%d%d%d", &a, &b, &k);
        printf("%d\n", solve());
    }

    return 0;
}

就是这样咯。