题解 CF1036C 【Classy Numbers】
数位DP
Solution
首先将询问 \([l,r]\) 分为两个前缀和相减 \([1,r]-[1,l-1]\) 方便计算。
考虑一个数成为 Classy Number 的限制:数字 \(1-9\) 的个数,记为 \(tot\),然后可以带进数位DP记搜模板:
int dfs(int pos/*位数*/,int .../*传的各种限制和变量*/,bool lim/*上限*/)
{
if(pos==0)return 1;//一般返回1
if(!lim&&f[pos][...]!=-1)
return f[pos][...];//搜过且不是上限就返回搜过的答案
int up=lim?a[pos]:9;//确定枚举上限
int res=0;
for(int i=0;i<=up;i++)
{
if(...)//限制条件
res+=dfs(pos-1,...,lim&&i==up);//如果本位已经枚举到上限就把上限往后传
}
if(!lim)f[pos][...]=res;//因为如果枚举到上限则答案并不是这一位上所有的和,所以就不更新
return res;
}
那么函数中的限制变量只有 \(tot\) ,枚举时判断它是否小于 \(3\) 即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ll long long
#define TT template<typename T>
namespace io{static streambuf *inbuf=cin.rdbuf();static streambuf *outbuf=cout.rdbuf();char buf[1<<21],*p1=buf,*p2=buf;inline char gc(){return (p1==p2&&(p2=(p1=buf)+inbuf->sgetn(buf,1<<21),p1==p2)?EOF:*p1++);}inline void pc(const char x){static streambuf *outbuf=cout.rdbuf();outbuf->sputc(x);}
inline int read(){register int _s=0,_f=1;register char _ch=gc();for(;!isdigit(_ch);_ch=gc())if(_ch=='-')_f=-1;for(;isdigit(_ch);_ch=gc())_s=_s*10+_ch-'0';return _s*_f;}TT inline void read(T &x){x=0;register int _f=1;register char _ch=gc();for(;!isdigit(_ch);_ch=gc())if(_ch=='-')_f=-1;for(;isdigit(_ch);_ch=gc())x=x*10+_ch-'0';x*=_f;}
TT inline void write(T _x1){if(_x1<0)pc('-'),_x1=-_x1;static char _sta[20];int _p=0;do{_sta[_p++]=_x1%10^48;_x1/=10;}while(_x1);while(_p--)pc(_sta[_p]);}TT inline void writeln(const T _x){write(_x);pc(10);}TT inline void writesp(const T _x){write(_x);pc(' ');}}using namespace io;
//io优化
ll f[20][4];//f[pos][tot]
int a[20],len;//将x分解为每一位
ll dfs(int pos,int tot,bool lim){
if(!pos)return 1;//枚举成功,加1
if(!lim&&f[pos][tot]!=-1)return f[pos][tot];
//只有包括全部才能直接记忆化
int up=lim?a[pos]:9;
ll res=0;
for(int i=0;i<=up;i++){
if(i&&tot==3)continue;//tot不能超过3
res+=dfs(pos-1,i?tot+1:tot,lim&&i==up);
}if(!lim)f[pos][tot]=res;
return res;
}
ll solve(ll x){
len=0;
do{//分解x
a[++len]=x%10;
x/=10;
}while(x);
return dfs(len,0,1);
}
int main(){
memset(f,-1,sizeof(f));
for(int T=read();T;T--){
ll l,r;read(l);read(r);
writeln(solve(r)-solve(l-1));
}
return 0;
}
\(trick:\) 记忆化数组 \(f\) 只用赋值一次 \(-1\) 因为这些状态在所有询问中都适用

浙公网安备 33010602011771号