数位Dp习题集

1.F - Variety of Digits (atcoder.jp)

        (一)题目大意

                给定你一个数N和M个限制,现在问你在不超过N的基础上并且有着M个限制,所有的数的和是多少,对998244353取模。

        (二)解题思路

                很明显的一个数位Dp的题目。

                由于只有十位限制,我们可以考虑状压,1<<i表示第i个数出现,我们规定两个数组s,f

           s[i][j]表示第i位状态为j的所有数的和,f[i][j]表示第i位状态为j的所有数的个数,对于每一位的上限,我们可以根据上一位是否处于限制决定我们当前这一位最高可以枚举多少,为了防止重复计数,我们采用zero标记是否是前导零位置。我们采用记忆化搜索,若这个位置不是前导0位,并且不处于限制位,并且已经遍历过了,那么我们直接return这个方案。

        (三)代码实现

#include "bits/stdc++.h"
#define PII pair<long long,long long>
#define fi first
#define se second
using namespace std;
const int N = 1e4 + 10,mod = 998244353;
long long t[N];
int s[N][1 << 11],f[N][1 << 11],g[N],st[12];
int cnt,res;
PII dfs(int pos,int sta,int flag,int zero)
{
    if(pos > cnt) return {sta == res,0};
    if(!zero && !flag && s[pos][sta] != -1) return {f[pos][sta],s[pos][sta]};
    int lim = flag ? g[pos] : 9;
    PII tmp = {0,0};
    for(int i = 0;i <= lim;i++) {
        PII xx;
        if(!i && zero) xx = dfs(pos + 1,sta,flag && i == lim,true);
        else if(st[i]) xx = dfs(pos + 1,sta | (1 << i),flag && i == lim,false);
        else xx = dfs(pos + 1,sta,flag && i == lim,false);
        tmp.first = (tmp.fi + xx.fi) % mod;
        tmp.second = (tmp.se + (xx.fi * i % mod * t[cnt - pos] % mod + xx.se) % mod) % mod;
    }
    if(!flag && !zero) s[pos][sta] = tmp.se,f[pos][sta] = tmp.fi;
    return tmp;
}
void solve()
{
    string z;
    cin >> z;
    for(int i = 0;i < z.size();i++) g[++cnt] = z[i] - '0';
    int n;
    memset(s,-1,sizeof s);
    cin >> n;
    for(int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        st[x] = 1;
        res |= 1 << x;
    }
    t[0] = 1;
    for(int i = 1;i <= cnt;i++) t[i] = t[i - 1] * 10 % mod;
    PII ans = dfs(1,0,true,true);
    cout << ans.se << endl;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    T = 1;
    while(T --) {
        solve();
    }
    return 0;
}

2.Problem - J - Codeforces

        (一)题目大意

                给你一个函数f(x),f(x)=∑i=1k−1∑j=i+1kd(x,i)⋅d(x,j)问你[L,R]区间中符合这个函数的数字个数又多少个f(x) = \sum_{i = 1}^{k - 1}\sum_{j = i + 1}^{k}d(x,i) *d(x,j),d(x,i)表示x这个数字的第i位数位。

               

 

        (二)解题思路

                考虑数位Dp,对于第i这个数位,我们加进来会产生前面加进来的数位和*i,需要减去的贡献是当前第i位数位有i*bit[i],最后取模m,因此我们把这个两个合并成一个状态,表示总共的数位和和这个数字的差模m的值,因此我们有dp[i][j][k][2],表示第i位前面和为j,差值为k,是否处于限制位的状态,为了更容易实现Dp,因此可以采用记忆化搜索方法实现。

        (三)代码实现

// Problem: J. Junior Mathematician
// Contest: Codeforces - 2019-2020 ICPC Asia Hong Kong Regional Contest
// URL: https://codeforces.com/gym/102452/problem/J
// Memory Limit: 512 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include "bits/stdc++.h"
#define rep(i, z, n) for (int i = z; i <= n; i++)
#define per(i, n, z) for (int i = n; i >= z; i--)
#define ll long long
#define db double
#define PII pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define yes cout << "YES" << endl;
#define no cout << "NO" << endl;
const int mod = 1e9 + 7;
using namespace std;
const int N = 5e3 + 10, M = 62;
int m, idx = 0;
int dp[N][M][M][2], vis[N][M][M][2], bit[N], dig[N];
char a[N], b[N];
int dfs(int num, int pre, int diff, int limit)
{
    if (num == 0)
    {
        return !diff;
    }
    if (vis[num][pre][diff][limit] == idx)
        return dp[num][pre][diff][limit];
    vis[num][pre][diff][limit] = idx;
    ll ans = 0;
    int up = limit ? dig[num] : 9;
    for (int i = 0; i <= up; i++)
    {
        int s = ((diff + i * pre - i * bit[num]) % m + m) % m;
        ans += dfs(num - 1, (i + pre) % m, s, limit && (i == up));
    }
    ans %= mod;
    return dp[num][pre][diff][limit] = ans;
}
int solve(char s[])
{
    int len = strlen(s + 1);
    bit[1] = 1;
    // memset(dig, 0, sizeof(dig));
    for (int i = 2; i <= len; i++)
    {
        bit[i] = bit[i - 1] * 10 % m;
    }
    for (int i = 1; i <= len; i++)
    {
        dig[i] = s[len - i + 1] - '0';
    }
    ++idx;
    if (idx % 2 == 0)
    {
        dig[1] -= 1;
        for (int i = 1; i < len; i++)
        {
            if (dig[i] < 0)
            {
                dig[i] += 10;
                dig[i + 1] -= 1;
            }
            else
                break;
        }
        while (dig[len] == 0)
            len--;
    }
    return dfs(len, 0, 0, 1);
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%s%s%d", a + 1, b + 1, &m);
        printf("%d\n", (solve(b) - solve(a) + mod) % mod);
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值