P2602 [ZJOI2010]数字计数

\(\color{#0066ff}{ 题目描述 }\)

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

\(\color{#0066ff}{输入格式}\)

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

\(\color{#0066ff}{输出格式}\)

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

\(\color{#0066ff}{输入样例}\)

1 99

\(\color{#0066ff}{输出样例}\)

9 20 20 20 20 20 20 20 20 20

\(\color{#0066ff}{数据范围与提示}\)

30%的数据中,\(a\leq b\leq 10^6\)

100%的数据中,\(a\leq b\leq 10^{12}\)

\(\color{#0066ff}{ 题解 }\)

一道基本的数位DP

本来以前做过的,代码太不清真了qwq,改一下

\(f[i][j][k]\)为长度为i,最高位为j,k出现的次数

#include<bits/stdc++.h>
using LL = long long;
LL in() {
	char ch; LL x = 0, f = 1;
	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
	return x * f;
}
LL f[25][10][10], base[16];
LL ans1[10], ans2[10];
LL l, r, a[20], num, b[20];
void init() {
	base[0] = 1;
	for(int i = 1; i <= 13; i++) { 
		base[i] = base[i - 1] * 10; 
		for(int j = 0; j <= 9; j++) 
			for(int k = 0; k <= 9; k++) {
				for(int l = 0; l <= 9; l++)
					f[i][j][l] += f[i - 1][k][l];
				if(j == k) f[i][j][j] += base[i - 1]; //最高位不动,后面随意的贡献
			}

		}
}
void query(LL p, LL *ans) {
	num = 0;
	while(p) {
		a[++num] = p % 10;
		b[num] = b[num - 1] + base[num - 1] * (p % 10);  //从num位开始的数
		p /= 10;
	}
	for(int i = 1; i < num; i++)
		for(int j = 1; j <= 9; j++)
			for(int k = 0; k <= 9; k++)
				ans[k] += f[i][j][k];   //不贴边的
	for(int i = 1; i < a[num]; i++)      //无前导零的贴边
		for(int j = 0; j <= 9; j++)
			ans[j] += f[num][i][j];
	ans[a[num]] += b[num - 1];      //作为某一位的贡献(后面什么数,最高位不变,统计贡献)
	for(int i = num - 1; i >= 1; i--) {
		for(int j = 0; j < a[i]; j++)
			for(int k = 0; k <= 9; k++)  //贴边统计
				ans[k] += f[i][j][k];
		ans[a[i]] += b[i - 1]; 
	}
}

int main() {
	init();
	l = in(), r = in();
	query(l, ans1);
	query(r + 1, ans2);
	for(int i = 0; i <= 9; i++) printf("%lld%c", ans2[i] - ans1[i] , i == 9? '\n' : ' ');
	return 0;
}
posted @ 2019-01-09 08:38  olinr  阅读(146)  评论(0编辑  收藏  举报