题意
求区间[a,b] 之间, x%f(x) = 0的数量, 其中f(x)表示x的数位和.
解题思路:
转换成 F(A) = { x | x%f(x) = 0, 1 <= x <= A } , 然后结果即为 F(B)-F(A-1)
首先定义一个状态. dp( L, i, j, k ) 表示长度为L的数位和为i,其对j取模结果为k的方案数.
在同模j的情况下,则在该数后面增加一个x(0,9), 则得到 dp( L+1, i+x, j, (k*10+x)%j ).
那么转移方程就是:
dp( L+1, i+x, j, (k*10+x)%j ) += dp( L, i, j, k )
接着就是如何利用已经得到的 dp( L, i, j, k )计算出 F(A).
假设 A长度为L, 则A可以这么表示: A = a1,a2,...,ai,...,aL. (a1为高位.)
则我们要求 所有的 B = b1,b2,...,bi,...,bl, 其中 B < A, l <= L,的所有满足 B%f(B) == 0的方案数.
按位枚举, A的每一位取值,得到B, 例如,已经处理到了第i位时, 分三种情况:
一. b1,..,bi-1 小于或等于 a1,...,ai-1 时: 则当前位置(i) bi 的取值为 [0,ai].
二. b1,...,bi-1 大于 a1,...,ai-1 时: 由条件一就保证其不会出现.
其实这里有两个技巧的地方.
1. 当 b1,...,bi-1 小于 a1,...,ai-1时, 那么我们后面的 bi,..,bL,就可以随意取了. 因为我们已经求得了 dp( L, i, j, k ) ,
就可以记忆化得出结果.
2. 因为我们让每一位的取值都从0 开始, 实际上我们这样就计算了长度小于L的.
具体处理细节看代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<string> #include<algorithm> using namespace std; typedef long long LL; const int N = 100; LL dp[10][N][N]; int Mod; int a[N], b[N], A[10]; LL res[N]; LL dfs( bool less, int length, int sum, int mod ){ if( length == 0 ) return less && (sum==Mod) && (mod==0); if( less && (dp[length][sum][mod]!=-1) ) return dp[length][sum][mod]; LL tmp = 0; for(int x = 0; x < 10; x++){ if( !less && (x>A[length-1]) ) break; tmp += dfs( less || (x<A[length-1]), length-1, sum+x, (mod*10+x)%Mod ); } if( less ) dp[length][sum][mod] = tmp; return tmp; } LL solve( int x ){//x本身不被计算 int tmp = x, L = 0; while( tmp ) A[L++] = tmp%10, tmp /= 10; //reverse( A, A+L ); LL tot = 0; return dfs( false, L, 0, 0 ); } int main(){ int t; scanf("%d", &t); for(int i = 0; i < t; i++) scanf("%d%d", &a[i], &b[i] ); memset( res, 0, sizeof(res)); for(Mod = 1; Mod < N; Mod++){ memset( dp, -1, sizeof(dp)); for(int i = 0; i < t; i++ ) res[i] += solve( b[i]+1 ) - solve( a[i] ); } for(int i = 0; i < t; i++) printf("Case %d: %lld\n", i+1, res[i] ); return 0; }