题目
给出一段区间a-b,统计这个区间内0-9出现的次数。
比如 10-19,1出现11次(10,11,12,13,14,15,16,17,18,19,其中11包括2个1),其余数字各出现1次。
一般情况来说让求 一个区间 a~b内满足某种数位条件的数字的个数等,都可以用数位dp来考虑。
数位dp其实算是暴力美学了吧(我觉得??),其主要通过记忆化搜索来实现,同时在剪枝的过程中降低复杂度。
先来说一下这道题。要统计每个数字出现的个数,首先我们0~9分开考虑。
先以‘1’举个例子,我们先考虑一个数abc,如果百位中出现了‘1’,那么以他开头的数字中一定含有‘1’,如果十位中也出现了,同样,所以我们考虑在dfs的过程中添加一个标记,来记录到当前位之前(包含当前位)一共出现了几次‘1’,也就是下文中dfs中的参数 yes。每次dfs的时候yes*当前的数位,就是这次dfs可以找到的满足条件的数。
ac代码
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define IO ios::sync_with_stdio(false);cin.tie(0)
const int maxn = 1005;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
ll dp[20][10][20]; //第一个状态记录第几位,第二个状态记录当前我们算的是哪个数的个书,第三个状态是yes的个数。
int a[20];
ll dfs(int pos,int n,int yes,bool lead,bool limit){
if(pos == -1) return 0;
if(!limit && !lead && dp[pos][n][yes] != -1) return dp[pos][n][yes];
int up = limit ? a[pos] : 9;
ll ans = up * yes; //这次dfs中通过之前的条件确定满足条件的个数
for(int i = 0;i <= up;i ++){
int flag = 0;
if(i == n) flag = 1;
if(i == 0 && lead && i == n) flag = 0; // 前导0的去除
ans += flag; // 通过当前数位判断满足条件,ans ++;
ll tmp = dfs(pos - 1,n,yes + flag,lead &&(i==0),(limit && i == a[pos]));
ans += tmp;
}
if(!limit&&!lead) dp[pos][n][yes] = ans;
return ans;
}
ll solve(ll x,int n){
int pos = 0;
while(x){
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,n,0,true,true);
}
int main() {
ll a,b;
cin >> a >> b;
memset(dp,-1,sizeof(dp));
for(int i = 0;i <= 9;i ++){
printf("%lld\n",solve(b,i)-solve(a - 1,i));
}
return 0;
}