HDU6796 X Number(数位DP)杭电多校

  • 题意:定义对于一个数字,那一个位数的数字出现最多,就定义为那个数字。比如133344就是状态3,111122就是状态1,对于出现次数不唯一的,定义为状态10.现在给一个区间问 ( l , r ) (l,r) (l,r)中有多少状态为 d d d的数, d d d不为10.
  • 做法:看一眼数位dp,然后状态怎么记录,考虑记录0-9每一个数字出现的次数,可以使用字符串, v e c t o r vector vector等使用 m a p map map标记,可以赌一下状态不会很多,甚至使用哈希,但不过这道题显然不会让你这么做,那么就需要优化。
  • 首先对于记录状态的 s t a sta sta来说,那么即使数字出现的位置不用那么也是相同的。这个优化,让我学到了。
  • 举个例子来说 1 − 12345678 1-12345678 112345678来说,如果遍历到 122 x x x x x 122xxxxx 122xxxxx其中 x x x范围全是 0 − 9 0-9 09。如果我们2成为众数,有多少中方案。这个时候就需要我们使用更快的方法求到答案。
  • 初始一共有 x x x个位置。那么我们可以枚举我们的众数填放的次数 i i i,我们考虑一个 d p [ p ] dp[p] dp[p],表示填完 p p p个位置的方案数,那么初始化就是 d p [ i ] = C x i dp[i]=C_{x}^i dp[i]=Cxi,然后枚举其他数填放的次数 j j j, 那么就有如下递推 d p [ i + j + k ] = C x − i − k j d p [ i + k ] dp[i+j +k]=C_{x-i - k}^jdp[i + k] dp[i+j+k]=Cxikjdp[i+k]其中 k k k表示的是除了当前枚举的数和众数占的位置。代码的 d p dp dp是倒着写的。
  • 然后对于其他有 l i m i t limit limit限制的就使用普通的数位dp就可以了。
  • 具体细节看代码吧.很容易T的.
#include "bits/stdc++.h"

using namespace std;
inline int read() {
    int x = 0;
    bool f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
    if (f) return x;
    return 0 - x;
}
#define SZ(x) ((int)(x.size()))
#define all(x) (x).begin(),(x).end()

const int maxn = 3000 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
using ll = long long;
int num[20], d;
ll l, r, c[22][22];
map<vector<int>, ll> dp[20][2][2];

ll comb(int n, int m) {
    if (c[n][m] != -1) return c[n][m];
    ll ans = 1;
    for (int i = m + 1; i <= n; i++) ans *= i;
    for (int i = 1; i <= n - m; i++) ans /= i;
    return c[n][m] = ans;
}
bool check(vector<int> sta) {
    for (int i = 0; i < 10; i++)
        if (i != d && sta[i] >= sta[d])
            return 0;
    return 1;
}

ll dfs(int pos, vector<int> sta, bool lead, bool limit) {
    if (pos == 0) return dp[pos][lead][limit][sta] = check(sta);
    if (dp[pos][lead][limit].find(sta) != dp[pos][lead][limit].end())
        return dp[pos][lead][limit][sta];
    ll ans = 0, up = limit ? num[pos] : 9;
    if (!limit && !lead) {
        for (int i = 0; i <= pos; i++) {
            vector<ll> p(20);
            p[pos - i] = comb(pos, i);
            for (int j = 0; j < 10; j++) {
                if (j == d) continue;
                if (sta[d] + i <= sta[j]) {
                    p[0] = 0;
                    break;
                }
                for (int k = 0; k <= pos - i; k++) {
                    for (int m = 1; m <= k; m++) {
                        if (m > sta[d] + i - sta[j] - 1) break;
                        p[k - m] += comb(k, m) * p[k];
                    }
                }
            }
            ans += p[0];
        }
    } else {
        for (int i = 0; i <= up; i++) {
            if (!lead || i != 0) sta[i]++;
            ans += dfs(pos - 1, sta, lead && i == 0, limit && i == up);
            if (!lead || i != 0) sta[i]--;
        }
    }
    return dp[pos][lead][limit][sta] = ans;
}


ll solve(ll x) {
    int tot = 0;
    while (x) {
        num[++tot] = x % 10;
        x /= 10;
    }
    vector<int> sta(10);
    for (int i = 0; i < 10; i++) sta[i] = 0;
    for (int i = 0; i < 20; i++)
        for (int j = 0; j < 2; j++)
            for (int k = 0; k < 2; k++)
                dp[i][j][k].clear();
    return dfs(tot, sta, 1, 1);
}

int main() {
    int T;
    memset(c, -1, sizeof(c));
    scanf("%d", &T);
    while (T--) {
        scanf("%lld%lld%d", &l, &r, &d);
        printf("%lld\n", solve(r) - solve(l - 1));
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值