P2602 [ZJOI2010]数字计数 (数位 DP)
经典数位 DP .
首先总结一个常见的方法,就是求区间 [ l , r ] [l,r] [l,r] 内符合条件的数有多少的时候,可以先求出 [ 0 , r ] [0,r] [0,r] 内符合条件的数 f r f_r fr,和 [ 0 , l ) [0,l) [0,l) 内符合条件的数 f l f_l fl,然后 f r − f l f_r-f_l fr−fl 就是答案。
回到本题,比如要求 [ 0 , n ] [0,n] [0,n] 内各个数码出现的次数,现在设 n n n 有 m m m 位,考虑其中一位 i i i 分别是 [ 0 , 9 ] [0,9] [0,9] 的方案数有多少。
首先考虑从简单的情况入手,比如 n = 99999 , m = 5 , i = 3 n= 99999,m=5,i=3 n=99999,m=5,i=3 ,再具体一点,我们令第 3 位为 5 ,考虑计算 5 在第 3 位出现的次数。当更高位已经确定的时候,也就是第 1 、第 2 位已经确定,那么 5 在第 3 位将出现 100 100 100 次,这 100 100 100 个数是 X X 500 , X X 501 , X X 502 , ⋯ , X X 598 , X X 599 \tt XX500 , XX501 , XX502,\cdots,XX598,XX599 XX500,XX501,XX502,⋯,XX598,XX599 ,然后根据乘法原理,我们知道第 1 、第 2 位有 10 × 10 = 100 10\times 10=100 10×10=100 种可能(考虑前导 0 ),那么 5 在第 3 位就要出现 10000 10000 10000 次,同理我们发现在 n = 99999 n = 99999 n=99999 的时候,每一个数字在第 i i i 位出现的次数都一样,是 1 0 p × 1 0 q 10^p\times10^q 10p×10q ,其中 p = m − i , q = i − 1 p = m-i,q=i-1 p=m−i,q=i−1 .
那么我们试着推广一下结论,比如当更高位有限制的时候, n = 47999 n=47999 n=47999 ,这时我们发现,对于 i = 3 i = 3 i=3 的时候更高位的选择只剩下 48 48 48 种情况(考虑前导 0 ),于是我们可以知道,当更高位有限制,而更低位没有限制的时候,每个数字在第 i i i 位出现的次数是 ( a 1 a 2 ⋯ a i + 1 ‾ + 1 ) × 1 0 i − 1 \left(\overline{a_1a_2\cdots a_{i+1}}+1\right)\times 10^{i-1} (a1a2⋯ai+1+1)×10i−1 ,其中 a 1 a 2 ⋯ a i − 1 ‾ = a 1 × 1 0 i − 2 + a × 1 0 i − 2 ⋯ + a i − 1 \overline{a_1a_2\cdots a_{i-1}}=a_1\times10^{i - 2}+a\times10^{i-2}\cdots+a_{i-1} a1a2⋯ai−1=a1×10i−2+a×10i−2⋯+ai−1
当我们再考虑更复杂的情况的时候,就要分类讨论了,现在求数码 k k k 在第 i i i 位出现的次数(考虑前导 0 ),令 d g t ( n , i ) dgt(n,i) dgt(n,i) 表示 n n n 中第 i i i 位的数码,那么:
- k < d g t ( n , i ) k < dgt(n,i) k<dgt(n,i) ,那么 k k k 将出现 ( a 1 a 2 ⋯ a i − 1 ‾ + 1 ) × 1 0 i − 1 \left(\overline{a_1a_2\cdots a_{i-1}}+1\right)\times10^{i-1} (a1a2⋯ai−1+1)×10i−1 次
- k = d g t ( n , i ) k=dgt(n,i) k=dgt(n,i) ,那么 k k k 将出现 a 1 a 2 ⋯ a i − 1 ‾ × 1 0 i − 1 + a i + 1 a i + 2 ⋯ a m ‾ \overline{a_1a_2\cdots a_{i-1}}\times10^{i-1} + \overline{a_{i+1}a_{i+2}\cdots a_m} a1a2⋯ai−1×10i−1+ai+1ai+2⋯am 次
- k > d g t ( n , i ) k > dgt(n,i) k>dgt(n,i) ,那么 k k k 将出现 a 1 a 2 ⋯ a i − 1 ‾ × 1 0 i − 1 \overline{a_1a_2\cdots a_{i-1}}\times10^{i-1} a1a2⋯ai−1×10i−1 次
接下来考虑消除前导 0 的影响,我的做法是结合题目所给的 a , b a,b a,b 计算 0 0 0 出现的次数,现在假设 b > a b > a b>a ,那么显然比 a a a 最高位要高的位数不能为 0 于是在枚举的时候调整一下枚举范围即可,当然也可以利用相减相消的特性消除前导 0 的影响。
int main()
{
pow10[0] = 1;for(int i = 1;i <= 12;i ++) pow10[i] = pow10[i - 1] * 10;
scanf("%lld%lld",&a,&b);
if(a > b) swap(a,b);
a --;//前缀和的思想
tmpa = a;tmpb = b;alen ++;aa[alen] = tmpa%10;tmpa /= 10;//处理出每一位的数码
for(;tmpa;){alen ++;aa[alen] = tmpa%10;tmpa /= 10;}
blen ++;bb[blen] = tmpb%10;tmpb /= 10;
for(;tmpb;){blen ++;bb[blen] = tmpb%10;tmpb /= 10;}
for(int i = blen;i >= 1;i --)//计算各种数出现的次数
{
for(int j = (i > alen ? 1 : 0)/*调整枚举边界*/;j < bb[i];j ++) ansb[j] += max(b / pow10[i] + 1,(ll)1) * pow10[i - 1];
for(int j = bb[i] + 1;j < 10;j ++) ansb[j] += max(b / pow10[i],(ll)0) * pow10[i - 1];
ansb[bb[i]] += (b / pow10[i])*pow10[i - 1] + (b % pow10[i - 1]) + 1;
}
if(a >= 0)
{
for(int i = alen;i >= 1;i --)
{
for(int j = 0;j < aa[i];j ++) ansa[j] += max(a/pow10[i] + 1,(ll)1) * pow10[i - 1];
for(int j = aa[i] + 1;j < 10;j ++) ansa[j] += max(a/pow10[i],(ll)0) * pow10[i - 1];
ansa[aa[i]] += (a / pow10[i])*pow10[i - 1] + (a % pow10[i - 1]) + 1;
}
}
for(int i = 0;i < 10;i ++) printf("%lld ",(a < 0 ? ansb[i] : ansb[i] - ansa[i]));//相减得到答案
return 0;
}