[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;
}

浙公网安备 33010602011771号