[ZJOI2010] 数字计数(数位DP)

题目

前缀和+记忆化搜索的方法:

时间复杂度:\(O(10^{2} L^{2})\)\(L\)为数字位数)

#include<bits/stdc++.h>
using namespace std;
long long a,b,f[25][25],dig[20];//f[pos][k]:记忆化数组,表示在“不受上界限制且没有前导零”的条件下,当前还要处理pos位,已经统计了k个目标数码时,后续能贡献的目标数码个数
long long dfs(int pos,bool eq,bool zero,int k,int d){//pos:当前处理到第pos位(从高位向低位);eq:是否贴紧上界,若eq=true,当前位最大只能取到a[pos],否则可以取到9;zero:是否有前导零,若zero=true,表示当前位置之前都是前导0,即当前数字尚未开始;k:已经统计到的数码d的出现次数;d:当前正在统计的数码(0~9)
	if(pos==0) return k;//表示一个完整的数字已经构造完毕,此时k就是该数字中数码d出现的总次数,直接返回
	if(!eq&&!zero&&f[pos][k]!=-1) return f[pos][k];//只有既不受上界限制,又不存在前导零的状态才可以用记忆化(原因嘛…留给读者自行思考~)
	int up=eq?dig[pos]:9;//确定当前位上限
	long long sum=0;
	for(int i=0;i<=up;i++){
		sum+=dfs(pos-1,
				 eq&&(i==dig[pos]),//下一位是否还贴紧上界
				 zero&&!i,//下一位是否仍是前导零
				 (zero&&!i)?0:(k+(i==d)),//更新已统计的数码次数
				 d);		      
	}
	if(!eq&&!zero) f[pos][k]=sum;//仅在普通状态下保存结果,下次遇到相同pos,k可以直接返回
	return sum;
}//函数返回值:在当前状态下,后续所有合法数字中数码d出现的次数总和
long long work(long long x,int d){//返回0~x中数码d出现的次数
	int num=0;
	while(x){//将x的每一位逆序存入dig
		dig[++num]=x%10;
		x/=10;
	}
	return dfs(num,1,1,0,d);//初始状态
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	cin>>a>>b;
	for(int i=0;i<=9;i++){
		memset(f,-1,sizeof(f));//每个数码分别计算,记忆化数组要清空
		cout<<work(b,i)-work(a-1,i)<<' ';
	}
	return 0;
}
posted @ 2026-08-16 14:52  decentz  阅读(21)  评论(0)    收藏  举报