P2602 [ZJOI2010]数字计数

题目描述

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

输入格式

输入文件中仅包含一行两个整数a、b,含义如上所述。

输出格式

输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

输入输出样例

输入
1 99

输出
9 20 20 20 20 20 20 20 20 20

说明/提示

30%的数据中,a<=b<=10^6;

100%的数据中,a<=b<=10^12。

思路

f[i]代表在有i位数字的情况下,每个数字有多少个。如果不考虑前导0,你会发现对于每一个数,它的数量都是相等的,也就是f[i]=f[i-1]*10+10^(i-1);(这里我推荐使用打表+大眼观察法)

然而这个公式推出来后,你就会面临第二个难题,怎么推出我想要的答案?

我们先设数字为ABCD

看A000,如果我们要求出它所有数位之和,我们会怎么求?

鉴于我们其实已经求出了0~9,0~99,0~999。。。上所有数字个数(f[i],且没有考虑前导0)我们何不把这个A000看成0000~1000~2000...A000对于不考虑首位每一个式子的数字的出现个数为 A*f[3]。加上首位出现也就是小于A每一个数都出现了10^3次,再加上,我们就把A000处理完了。

这样你以为就把第一位处理完了?不不不,首位A还出现了BCD+1次呢,也就是从A000~ABCD,这个A还出现了BCD+1次,所以再加上这些才行呢。那么你发现,我们成功把首位代表的所有数字个数求出来了,剩下的求解与A完全没有任何关系,只是BCD的求解,于是我们发现我们已经把一个大问题,化成了一个个小问题,也即是,对于一个这样n位的数,把他一位位的分离开来。

当然你还需要处理前导0你会发现前导0一定是0001,0002。。。0012,0013。。。0101,0102.。。0999这样的数,一共出现了10*(i-1)+10*(i-2)+...10 (i表示数字位数),让0的统计减去这个值,那么恭喜你这道题做完了。

心得

 对于DP这个东西,最重要的其实只有一点,推状态,状态又是什么?是大问题的子问题,对于这种题最重要的特点是,无后效性,问题可拆分,并且答案的求解具有一定的规律,这样的题应该就可以用DP做,数位DP最重要的就是把一整个数字拆分成一位一位的单独来看,那么对于数位DP,它的子问题也就一般是每一位上对于答案的求解,层层递进的这么一个思路。

 

代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int N=50;

long long a,b;
long long t[N],f[N];
long long cnta[N],cntb[N];

void solve(long long x,long long *cnt) {
	long long num[20]= {0};
	int len=0;
	while(x) {
		num[++len]=x%10;
		x=x/10;
	}
	for(int i=len; i>=1; i--) {
		for(int j=0; j<=9; j++)
			cnt[j]+=f[i-1]*num[i];
		for(int j=0; j<num[i]; j++)
			cnt[j]+=t[i-1];
		long long num2=0;
		for(int j=i-1; j>=1; j--)
			num2=num2*10+num[j];
		cnt[num[i]]+=num2+1;
		cnt[0]-=t[i-1];
	}
}

int main () {
	scanf("%lld%lld",&a,&b);
	t[0]=1;
	for(int i=1; i<=15; i++) {
		f[i]=f[i-1]*10+t[i-1];
		t[i]=10*t[i-1];
	}
	solve(a-1,cnta);
	solve(b,cntb);
	for(int i=0; i<=9; i++)
		printf("%lld ",cntb[i]-cnta[i]);
	printf("\n");
	return 0;
}

 

 

转载于:https://www.cnblogs.com/mysh/p/11247775.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值