P2602 [ZJOI2010]数字计数 - 数位DP

首先做一下预处理,一般都是DP(递推)处理
对于给定数来说,那些位数比他少的数字一定对他的答案有贡献
所以无需考虑这些位数少的数是否超越给定数,因而可以直接把全部贡献都算上
这个时候预处理就发挥作用了,假设这个数n是4位数吧,那么预处理出0 ~ 999中,每个数码出现的次数
考虑DP(递推)处理(大概是套路),设\(f(i,j)表示有i位的数字中数码j出现的次数,\)那么可以得到递推式

\[f(i, j) = f(i-1, j)*10 + 10^{i-1} \]

这个找几个位数少的数,大力观察就得到了
对于乘10,考虑一个数ABC,多了一位变成0ABC,1ABC,2ABC...(暂时承认000x这样的存在)
对于加上10的幂,考虑ABC,多了一位,A000 ~ A999,多了1000个A

注意这是一个独立的状态式,他只代表这个状态的答案,之前状态中的那些数码即使被计数过一次,到了这个状态还要被计数一次,因为这不是什么“和”,他只表示长度为i位的数字中数码j出现的次数,因此上面式子中乘10,和10的幂这两个项并没有重复的方案,他是从两个角度,互补统计了数码的个数
举个栗子:对于11这个数,以“乘10”的方式,仅统计了个位的1加到答案中;以“10的幂”的方式,统计了十位的1,加到答案中

但仍有问题,我们把00000x看做一个数字了,也就是说我们强行把低位数补0补到高位,这是因为100007这样的数需要统计0的个数,但是这个过程实际是1 和 00007拼在了一起,为了统计不能不计算前导0。
没问题,只需要删除多余的前导0,就能统计0的正确个数。那么像00007这样的数,7多算了吗?并没有,我只是把低位数补0,这个低位数本身仍然只出现了一次,所以7应该算上贡献

如何删除前导0,大力观察后我们发现,对于一个n位数,假设4位吧,从0000到0999
他的前导0数量之和是这么算出来的:10 + (10 + 90)+ (10 + 90 + 900),貌似可以递推求解。但是还有一个问题,就是关于“0”的处理,单独的0不能算作前导0,但是也不会被统计到任何区间内部(因为题目给的是两个正整数),同时,为了算出10,100这样的数中0的个数,又不能在预处理的时候不把f[1][0]赋为1,只好在最后,把多余的0再减去1

最终删除0的数量:

\[g(i)=\sum_{i=0}^{n-1}10^{i} \]

预处理结束,剩下的部分就是数位dp的基本操作了,统计完位数低的,开始算位数相同的,仍然是把最高位小于给定数的全部统计,然后考虑等于的情况,不断“试填”数字

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 30;
typedef long long ll;
ll a,b,cnt_a[MAXN],cnt_b[MAXN],num[MAXN],f[MAXN][MAXN],n,pow_ten[MAXN],suf[MAXN];
void dig_init() {
	for(int i=0; i<=9; i++) {
		f[1][i] = 1;
	}
	pow_ten[0] = 1; 
	for(int i=1; i<=12; i++) {
		pow_ten[i] = pow_ten[i-1] * 10;
	}
	for(int i=2; i<=12; i++) {
		for(int j=0; j<=9; j++) {
			f[i][j] = f[i-1][j] * 10 + pow_ten[i-1];
		}
	}
}

void work(ll k, ll *cnt) {
	memset(suf, 0, sizeof(suf));
	n = 0;
	while(k) {
		num[++n] = k % 10;
		k /= 10;
		suf[n] = suf[n-1] + num[n] * pow_ten[n-1];
	}
	ll delta = 0;
	for(int i=0; i<n; i++) {
		delta += pow_ten[i];
	}
	cnt[0] -= delta;
	while(n) {
		for(int i=0; i<num[n]; i++) {
			cnt[i] += pow_ten[n-1];
			for(int j=0; j<=9; j++) {
				cnt[j] += f[n-1][j];
			}
		}
		cnt[num[n]] += suf[n-1] + 1;
		n--;
	}
}

int main() {
	cin >> a >> b;
	dig_init();
	work(a-1, cnt_a), work(b, cnt_b);
	for(int i=0; i<=9; i++) {
		cout << cnt_b[i] - cnt_a[i] << " ";
	}
	return 0;
}
posted @ 2018-10-22 15:35  Zolrk  阅读(197)  评论(0编辑  收藏  举报