【数位dp】bzoj1799: [Ahoi2009]self 同类分布
各种奇怪姿势的数位dp
Description
给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
Sample Input
10 19
Sample Output
3
HINT
【约束条件】1 ≤ a ≤ b ≤ 10^18
题目分析
好像10^18左右的数位dp都是乱搞就好了
既然是要求整除原数,那么数位之和肯定要放进状态里,并且枚举数位总和地去dp。
看上去好像$f[i][j]$表示后面$i$位总和为$j$的合法方案数不就好了吗?
考虑这种状态的转移会发现,从后面做上来不可行啊,没办法处理在开头加上一个数后,能整除数位之和的方案。并且我们启发性地发现,为了转移的合法性,需要在状态里加上余数来限制状态(因为当前不能整除的或许后面能够整除了;反之亦有可能)。
于是想到用$f[i][j][k][done(0/1)]$表示前$i$位和为$j$,除数位总和$sum$的余数为$k$,是否达到上限(done=1表示达到)的合法状态数。
最终答案显然是$\sum{f[digits][sum][0][0]+f[digits][sum][0][1]}$。当然这个sum是要枚举过去的。
如此,转移也就不难处理了,并且时间复杂度也是正确的(位数最大就18)
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 4 ll a,b; 5 ll f[23][203][203][2]; 6 int digit[23]; 7 8 ll read() 9 { 10 char ch = getchar(); 11 ll num = 0; 12 bool fl = 0; 13 for (; !isdigit(ch); ch = getchar()) 14 if (ch=='-') fl = 1; 15 for (; isdigit(ch); ch = getchar()) 16 num = (num<<1)+(num<<3)+ch-48; 17 if (fl) num = -num; 18 return num; 19 } 20 ll solve(ll x) 21 { 22 for (digit[0]=0; x; x/=10) 23 digit[++digit[0]] = x%10; 24 for (int i=1; i<=digit[0]/2; i++) 25 std::swap(digit[i], digit[digit[0]-i+1]); 26 int mx = digit[0]*9; 27 ll ret = 0; 28 for (int sum=1; sum<=mx; sum++) 29 { 30 memset(f, 0, sizeof f); 31 f[0][0][0][1] = 1; 32 for (int i=0; i<digit[0]; i++) 33 for (int j=0; j<=i*9; j++) 34 for (int k=0; k<sum; k++) 35 for (int p=0; p<=1; p++) 36 if (f[i][j][k][p]) 37 for (int t=0; t<=9; t++){ 38 if (p&&digit[i+1] < t) break; 39 f[i+1][j+t][(10*k+t)%sum][p&&digit[i+1]==t] += f[i][j][k][p]; 40 } 41 ret += f[digit[0]][sum][0][0]+f[digit[0]][sum][0][1]; 42 } 43 return ret; 44 } 45 int main() 46 { 47 a = read(), b = read(); 48 printf("%lld\n",solve(b)-solve(a-1)); 49 return 0; 50 }
END