CF1036C Classy Numbers 题解
题意
问从 \(l\) 到 \(r\) 有多少个数的十进制表示有不超过 \(3\) 个非零位。
分析
一道数位 dp 的模板题。从后向前枚举每一位,如果这一位放 \(0\) ,就从前一位、非零位个数相同转移,否则从前一位,非零位个数 \(+1\) 转移。
代码
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a));
#define elif else if
using namespace std;
typedef long long ll;
ll T,l,r,dp[27][7];
vector<int>a;
ll dfs(int pos,int cnt,bool limit)
{
if(pos==-1) return 1; //枚举到头了
if(!limit&&dp[pos][cnt]!=-1) return dp[pos][cnt]; //如果之前已经得出答案了
ll ans=0;
int up=limit?a[pos]:9; //如果之前放满了,这一位就只能枚举到a[pos],否则能枚举到9
for(int i=0;i<=up;i++)
{
if(i==0)
{
ans+=dfs(pos-1,cnt,limit&&i==a[pos]); //如果这一位放0,就从前一位、非零位个数相同转移
}
elif(cnt<3) ans+=dfs(pos-1,cnt+1,limit&&i==a[pos]); //如果不放0,就从前一位,非零位个数+1转移
}
return limit?ans:dp[pos][cnt]=ans;
}
ll solve(ll x)
{
a.clear();
int pos=0;
while(x) //拆位
{
a.push_back(x%10);
x/=10;
pos++;
}
return dfs(a.size()-1,0,true); //从后向前枚举
}
int main()
{
scanf("%lld",&T);
mem(dp,-1); //赋一下初始值,方便记忆化
while(T--)
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}