P2602 [ZJOI2010]数字计数 (数位 DP)

P2602 [ZJOI2010]数字计数 (数位 DP)

P2602 [ZJOI2010]数字计数

经典数位 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 frfl 就是答案。

回到本题,比如要求 [ 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=mi,q=i1 .

那么我们试着推广一下结论,比如当更高位有限制的时候, 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} (a1a2ai+1+1)×10i1 ,其中 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} a1a2ai1=a1×10i2+a×10i2+ai1

当我们再考虑更复杂的情况的时候,就要分类讨论了,现在求数码 k k k 在第 i i i 位出现的次数(考虑前导 0 ),令 d g t ( n , i ) dgt(n,i) dgt(n,i) 表示 n n n 中第 i i i 位的数码,那么:

  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 ) × 1 0 i − 1 \left(\overline{a_1a_2\cdots a_{i-1}}+1\right)\times10^{i-1} (a1a2ai1+1)×10i1
  2. 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} a1a2ai1×10i1+ai+1ai+2am
  3. 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} a1a2ai1×10i1

接下来考虑消除前导 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值