BZOJ1833 [ZJOI2010]count 数字计数 【数学 Or 数位dp】

题目

给定两个正整数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。

题解

你以为我真的会写数位dp?

首先容斥一下,转化为求小于等于n的方案数

如果不考虑前缀0,那么就只需要递归处理不大于n所有数字出现的次数
考虑前缀0,我们再减去开头有若干个0的方案数

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 15,maxm = 100005,INF = 1000000000;
LL g[maxn],Pow[maxn];
void init(){
	Pow[0] = 1;
	for (int i = 1; i < maxn; i++) Pow[i] = Pow[i - 1] * 10;
	g[1] = 1;
	for (int i = 2; i < maxn; i++){
		g[i] = 10 * g[i - 1] + Pow[i - 1];
	}
}
struct node{
	LL t[10];
	node(){memset(t,0,sizeof(t));}
};
node cal(LL n,LL h,LL tmp){
	//cout << n << endl;
	node re,t;
	if (h == 1){
		for (int i = 0; i <= n; i++) re.t[i] = 1;
		return re;
	}
	for (int i = 0; i <= 9; i++){
		re.t[i] += (n / tmp) * g[h - 1];
		if (i < n / tmp) re.t[i] += Pow[h - 1];
	}
	re.t[n / tmp] += n - (n / tmp) * tmp + 1;
	t = cal(n % tmp,h - 1,tmp / 10);
	for (int i = 0; i <= 9; i++) re.t[i] += t.t[i];
	return re;
}
node solve(LL n){
	LL h = 1,tmp = 1;
	for (LL i = n; i / 10; i /= 10) h++,tmp *= 10;
	node re = cal(n,h,tmp);
	for (int i = 1; i < h; i++){
		re.t[0] -= Pow[h - i];
	}
	return re;
}
int main(){
	init();
	LL a,b;
	cin >> a >> b;
	node ansr = solve(b),ansl = solve(a - 1);
	for (int i = 0; i < 9; i++) printf("%lld ",ansr.t[i] - ansl.t[i]);
	printf("%lld",ansr.t[9] - ansl.t[9]);
	return 0;
}

posted @ 2018-04-07 17:08  Mychael  阅读(199)  评论(0编辑  收藏  举报