求一个区间内满足某种限制条件的数有多少个,是一个经典的数位 dp 问题。
设 \(f_{i,j,k,l}\) 表示由 \(i\) 位数字构成、各位数字之和是 \(j\)、对 \(k\) 取模余数是 \(l\) 的数有多少个。
在计算 \(f\) 时,允许前导 \(0\) 的存在,枚举第 \(i\) 位的数字 \(p\),得到状态转移方程:
闭区间 \([L, R]\) 种月之数的个数,等于 \([1, R]\) 种月之数的个数减去 \([1, L-1]\) 种月之数的个数。接下来以 \([1,R]\) 进行说明。
采取 “试填法” 的思想,从高位到低位给每一位填数,只要填了一个比上限 \(R\) 小的数位,那么后边的数位无论是多少,整个数值都不会超过 \(R\),此时就可以立即把 dp 预处理出的结果累加到答案中。只有在每一位上始终填写与上限 \(R\) 相同的数字时,才需要继续向后扫描,所以最终的计算量是数值的 “位数” 级别的,非常小。
我们枚举最终的各位数字之和 \(sum\),然后从左到右扫描每个数位,设当前正在处理第 \(i\) 位(最高位为第 \(N\) 位,最低位为第 \(1\) 位),当前已经填写的数字之和是 \(t\),当前数值对 \(sum\) 取模余数是\(q\),我们从小到大枚举第 \(i\) 位要填的数字 \(p\)。
若 \(p\) 小于上限 \(R\) 在第 \(i\) 位上的数字,则后边 \(i - 1\) 位可以随便填,因为最终的数值能被 \(sum\) 整除,所以第 \(1 \sim i\) 位构成的数值对 \(sum\) 取模的余数应该是 \(sum - q\),因此答案直接累加 \(f_{i-1,sum-t-p,sum,(sum-q-p \cdot 10^{i-1}) \mod sum}\)
否则,令 \(t = t + p, q = q + p \cdot 10^{i-1} \mod sum\),开始处理第 \(i-1\) 位 \([1, L-1]\) 同理。
代码:
#include<bits/stdc++.h>
using namespace std;
const int SIZE=1e5+10;
int L,R;
int dp[12][85][85][85],s[85][10];
int num[12];int val(int pos,int sum,int mod,int x,bool flag) {if(x<sum) return 0;if(!flag) return dp[pos+1][x][x-sum][(x-mod)%x]; if(pos==-1) return (mod==0&&sum==x);int res=0;for(int d=0; d<=num[pos]; d++) {bool e=(d==num[pos]);res+=val(pos-1,sum+d,(mod+(s[x][pos])*d)%x,x,e);}return res;
}int Calc(int x) {int len=0,res=0;while(x) num[len++]=x%10,x/=10;for(int i=1; i<=81; i++) res+=val(len-1,0,0,i,true);return res;
}void pre() {for(int i=1; i<=81; i++) {memset(dp[0][i],0,sizeof(dp[0][i]));dp[0][i][0][0]=1;s[i][0]=1%i;for(int j=1; j<=9; j++) s[i][j]=(s[i][j-1]*10)%i;for(int j=1; j<=9; j++)for(int k=0; k<=j*9; k++)for(int p=0; p<=i; p++)for(int q=0; q<=9&&k>=q; q++) dp[j][i][k][p]+=dp[j-1][i][k-q][((p-s[i][j-1]*q)%i+i)%i];}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);pre();while(cin>>L>>R) cout<<Calc(R)-Calc(L-1)<<endl;return 0;
}