题解 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\) 因为这些状态在所有询问中都适用

posted @ 2020-11-29 22:32  ADay526  阅读(145)  评论(0)    收藏  举报