[CF55D] Beautiful numbers

前言:

主播曾经只会递推形式的数位dp,最近学会了记忆化搜索的,写点东西记录下。

题目描述:

给定 \(l\), \(r\), \(k\),求 \([l,r]\) 之间的各数位上包含的不同数字不超过 \(k\) 个的所有数的和。答案对 \(998244353\) 取模。

保证 \(1 \leq l \leq r \leq 10, 1 \leq k \leq 10\)

解题思路:

设计状态 f[n][lim1][lim2][s] 表示到第 \(n\) 位,前面有无非 \(0\) 的数,是否到上界,出现的数的集合为 \(s\) 的数的和,g[n][lim1][lim2][s] 同理,只不过记的是合法的数的个数。

记录前面有无非零的数是为了判断前导零,那么搜索时枚举这一位填什么,判断是否顶到上界,转移显然。

代码实现:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
int l, r, k, f[30][2][2][1 << 10], g[30][2][2][1 << 10], a[30], fac[30];//第i位,前面有无非0的数,是否到上界,数的集合
int calc1(int x){
    int res = 0;
    while(x) res += (x & 1), x >>= 1;
    return res;
}
void add(int &x, int y){x = (x + y) % mod;}
pair<int, int> dfs(int n, int lim1, int lim2, int s){
    if(n == 0){
        if(calc1(s) <= k) return make_pair(0, 1);
        else return make_pair(0, 0);
    }
    if(f[n][lim1][lim2][s] != -1) return make_pair(f[n][lim1][lim2][s], g[n][lim1][lim2][s]);
    f[n][lim1][lim2][s] = g[n][lim1][lim2][s] = 0;
    for(int x = 0; x <= (lim2 ? a[n] : 9); x++){
        add(f[n][lim1][lim2][s], dfs(n - 1, lim1 | (x != 0), lim2 & (x == a[n]), (lim1 | (x != 0)) ? (s | (1ll << x)) : s).first);
        add(f[n][lim1][lim2][s], ((fac[n - 1] * x % mod) * dfs(n - 1, lim1 | (x != 0), lim2 & (x == a[n]), (lim1 | (x != 0)) ? (s | (1 << x)) : s).second % mod));
        add(g[n][lim1][lim2][s], dfs(n - 1, lim1 | (x != 0), lim2 & (x == a[n]), (lim1 | (x != 0)) ? (s | (1ll << x)) : s).second);
    }
    return make_pair(f[n][lim1][lim2][s], g[n][lim1][lim2][s]);
}
int calc(int x){
    memset(f, -1, sizeof f);
    memset(g, -1, sizeof g);
    int tot = 0;
    while(x) a[++tot] = x % 10, x /= 10;
    return dfs(tot, 0, 1, 0).first;
}
signed main(){
    fac[0] = 1;
    for(int i = 1; i <= 20; i++) fac[i] = fac[i - 1] * 10 % mod;
    cin >> l >> r >> k;
    cout << (calc(r) - calc(l - 1) + mod) % mod << endl;
    return 0;
}
posted @ 2025-06-30 14:55  _huangweiliang  阅读(10)  评论(0)    收藏  举报