E. Segment Sum (数位DP)

题目:传送门

题意:问在区间 [ L, R ] 中使用的数字不超过 k 种的数的和是多少,例如区间 [ 10, 50 ] 中使用的数字不超过 1 种的数的和是 11 + 22 + 33 + 44 = 110.

   1 <= L <= R < 1e18, 1 <= k <= 10, 输出答案对 998244353 取模。

题解:显然数位DP,不过这里要算的是数的和,不是数的个数,那我们再维护一个变量就行啦,维护一下每个位上的数的贡献。

#include <bits/stdc++.h>
#define LL long long
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF INT_MAX
#define inf LLONG_MAX
#define PI acos(-1)
using namespace std;

const int N = 1e6 + 5, mod = 998244353;

int a[20];
int k;
LL p[25];
pair < LL, LL > dp[20][1 << 15];

pair < LL, LL > dfs(int pos, int statu, bool zero, bool limit) {
    if(!pos) return make(__builtin_popcount(statu) <= k, 0); /// 贡献在前面已经算过
    if(!limit && !zero && dp[pos][statu].first != -1) return dp[pos][statu];
    int up = limit ? a[pos] : 9;
    pair < LL, LL > ans = make(0, 0);
    rep(i, 0, up) {
        pair < LL, LL > res;
        if(zero && i == 0) res = dfs(pos - 1, statu, zero, limit && i == up); ///前导0,这些0不算用过0.
        else res = dfs(pos - 1, statu | (1 << i), zero && i == 0, limit && i == up);
        ans.first = (ans.first + res.first) % mod;
        ans.second = (ans.second + res.second + i * res.first % mod * p[pos - 1] % mod) % mod;
    }
    if(!limit && !zero) dp[pos][statu] = ans;
    return ans;
}

LL cal(LL x) {
    int tot = 0;
    while(x) {
        a[++tot] = x % 10;
        x = x / 10;
    }
    mem(dp, -1);
    return dfs(tot, 0, 1, 1).second;
}

void solve() {
    LL l, r;
    p[0] = 1LL;
    rep(i, 1, 20) p[i] = (p[i - 1] * 10LL) % mod;
    scanf("%lld %lld %d", &l, &r, &k);
    LL ans = (cal(r) - cal(l - 1) + mod) % mod;
    printf("%lld\n", ans);
}

int main() {

    solve();

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Willems/p/12389841.html