P2602 [ZJOI2010]数字计数(数位DP/好题)

题目描述

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

输入格式

仅包含一行两个整数 a,b,含义如上所述。

输出格式

包含一行十个整数,分别表示 0∼9 在 [a,b]中出现了多少次。

输入输出样例

输入 #1复制

1 99

输出 #1复制

9 20 20 20 20 20 20 20 20 20

说明/提示

数据规模与约定

  • 对于 30%30% 的数据,保证 a≤b≤10^6;
  • 对于 100%100% 的数据,保证 1≤a≤b≤10^12。

大概就是魔改过的数位DP板题。设四维状态分别是剩余的长度(或者说当前的位置(从右到左))len,上一个字符last,之前部分是否紧贴上界的判定标志flag,之前部分是否全0的判定标志zero。每个状态的值为一个pair,first是这个状态下一共有多少符合的数,second是这些数里当前要计算的digit(作为dfs的参数)出现的次数。

特别注意有一个地方需要判前导0,详情见代码注释QAQ。
写完翻了翻题解发现有简洁一万倍的写法https://www.luogu.com.cn/blog/bestFy0731/solution-p2602

#include <bits/stdc++.h>
//#define int long long
#define ll long long
using namespace std;
ll a, b, num[65], len;
pair<ll, ll> dp[65][65][2][2];
ll ans[2][10];
pair<ll, ll> dfs(int len, int last, bool flag, bool zero, int digit) {
	if(len == 0) return make_pair(1, 0);//注意second是0,一开始写的是last == digit 但是可能和后面的total_digit += sum1;冲突 
	if(dp[len][last][flag][zero].first != -1) return dp[len][last][flag][zero];//记忆化
	ll sum = 0, total_digit = 0;//计算当前状态dp pair用的变量 sum是当前状态的数的个数 total_digit是当前状态中digit出现的总次数
	for(int i = 0; i <= 9; i++) {
		ll sum1 = 0, total_digit1 = 0;
		pair<ll, ll> tmp;
		if(((flag && i <= num[len]) || !flag) ) {//当前是可行的(指不超出上界)
			tmp = dfs(len - 1, i, flag && (num[len] == i), zero && (i == 0), digit);//搜索下一个状态的套路写法
			sum1 = tmp.first;
			total_digit1 = tmp.second;
		}
		sum += sum1;//这个更新是显然的
		total_digit += total_digit1;//这个更新也是显然的
		if(i == digit && !(zero && i == 0)) total_digit += sum1;//很重要!不能漏掉条件
		//这里是说当前遍历的数字正好是digit 这时候需要total_digit += sum1;因为这种状态有sum1个数,每个数前面加上一个digit,对total_digit的贡献就是sum1(这种状态的数的数量)
		//注意这里要加上判定条件,即排除前导0的情况
		//可能会问为什么前面两条更新语句不需要这个条件,因为前两条当前为0且之前全为0但后继状态的贡献与此无关
		//但是下一个状态每个数前面加上一个digit得到的应该是一个实际存在的合法的数,且与当前状态有关,因此需要判定
	}
	pair<ll, ll> ret;
	ret.first = sum;
	ret.second = total_digit;
	return (dp[len][last][flag][zero] = ret);
}
ll solve(ll x, int digit) {
	len = 0;
	ll xx = x;
	while(xx) {
		ll now = xx % 10;
		xx /= 10;
		len++;
		num[len] = now;
	}
	for(int i = 0; i < 65; i++) {
		for(int j = 0; j < 65; j++) {
			for(int k = 0; k < 2; k++) {
				for(int w = 0; w < 2; w++) {
					dp[i][j][k][w].first = -1;
					dp[i][j][k][w].second = 0;
				}
			}
		}
	}
	pair<ll, ll> tmp = dfs(len, 11, 1, 1, digit);
	return tmp.second;
} 
signed main() {
	cin >> a >> b;
	for(int i = 0; i < 10; i++) {//对于每个数单独算
		cout << solve(b, i) - solve(a - 1, i) << " ";
	}
	return 0;
}
原文地址:https://www.cnblogs.com/lipoicyclic/p/14716387.html