数位DP

数位DP

数位DP通常适用于以下问题:

给定一个区间 [ l , r ] [l,r] [l,r],给定一个条件函数 C ( n ) C(n) C(n),若数 n n n满足条件 C C C,则 C ( n ) C(n) C(n)的值为 1 1 1,否则为 0 0 0。求在这个区间满足条件 C C C的数的个数有几个?

数位DP,顾名思义,就是在数的个位,十分位,百分位,……上做DP。

我们定义:

d p ( p o s , p r e , l i m i t ) dp(pos,pre,limit) dp(pos,pre,limit)表示从低位开始算起,计算第 p o s pos pos位的值符合条件的数的数量。

其中:

p r e pre pre表示 p o s + 1 pos+1 pos+1位上的数, l i m i t limit limit表示到 p o s + 1 pos+1 pos+1位为止,是否存在限制。

这里的限制是指,假设枚举小于 2345 2345 2345的四位数,枚举到第 4 4 4为,此时第四位上的数可以为 0 , 1 , 2 0,1,2 0,1,2,因为必须小于 2345 2345 2345所以小于的限制没有解除。如果第四位为2,那么第三位的上限为 3 3 3而不是 9 9 9,因为小于的限制仍然没有解除。如果第四位为 1 1 1,那么后面的数位的上限都是 9 9 9,因为小于的限制已经解除了,这就是 l i m i t limit limit状态的作用了。

模板例题

HDU 2089

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

typedef long long ll;

int arr[10];

int dp[10][2][2];
// dp[pos][withTwo][limit]
// dp[满足第0-pos位][pos位计数时带不带2][有没有限制]

int dfs(int pos, bool withTwo, bool limit)
{
    // 如果到了末尾返回1
    if (pos == -1)
        return 1;

    // 如果有记录直接查表
    if (dp[pos][withTwo][limit] != -1)
    {
        return dp[pos][withTwo][limit];
    }

    // 计算上限
    int up = limit ? arr[pos] : 9;

    int cnt = 0;

    // 枚举数位
    for (int i = 0; i <= up; i++)
    {

        // 如果该位不带2,但是遍历到2,或者遍历到4,结束当前遍历
        if ((!withTwo && i == 2) || i == 4)
            continue;

        // 求到[0,pos-1]为止,带不带2由i != 6决定,限制为固定写法
        cnt += dfs(pos - 1, i != 6, limit && i == arr[pos]);
    }

    dp[pos][withTwo][limit] = cnt;

    return cnt;
}

// 前缀和函数,求满足[0,up]区间内的数字的个数
int psum(int up)
{
    memset(dp, -1, 40 * sizeof(int));
    int pos = 0;
    while (up > 0)
    {
        arr[pos++] = up % 10;
        up /= 10;
    }
    return dfs(pos - 1, true, true);
}

int main()
{
    int l, r;
    for (;;)
    {
        scanf("%d %d", &l, &r);
        if (l == 0 && r == 0)
            break;
        // 前缀和做减法
        printf("%d\n", psum(r) - psum(l - 1));
    }

    return 0;
}

P2602 数字计数

#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
using namespace std;
typedef long long ll;

ll dp[15][2][15][2];
ll acc[15][2];
ll arr[15];

ll dfs2(int pos, int limit)
{
    if (pos == -1)
        return 1;

    if (acc[pos][limit] != -1)
    {
        return acc[pos][limit];
    }

    int upper = limit ? arr[pos] : 9;

    ll ans = 0;

    for (int i = 0; i <= upper; i++)
    {
        ans += dfs2(pos - 1, limit && i == upper);
    }

    return acc[pos][limit] = ans;
}

ll dfs(int pos, int limit, int bit, int leader)
{
    if (pos == -1)
        return 0;

    if (dp[pos][limit][bit][leader] != -1)
    {
        return dp[pos][limit][bit][leader];
    }

    int upper = limit ? arr[pos] : 9;

    ll ans = 0;

    for (int i = 0; i <= upper; i++)
    {
        ans += dfs(pos - 1, limit && i == upper, bit, leader && i == 0);
        if (i != 0 || !leader)
        {
            ans += (i == bit) * (pos != 0 ? acc[pos - 1][limit && i == upper] : 1);
        }
    }

    return dp[pos][limit][bit][leader] = ans;
}

ll psum(ll x, int bit)
{
    memset(dp, -1, sizeof(dp));
    memset(acc, -1, sizeof(acc));
    int i = 0;
    for (; x; x /= 10, i++)
    {
        arr[i] = x % 10;
    }
    dfs2(i - 1, true);

    return dfs(i - 1, 1, bit, 1);
}

int main()
{
    ll a, b;
    scanf("%lld %lld", &a, &b);
    for (int bit = 0; bit <= 9; bit++)
    {
        printf("%lld ", psum(b, bit) - psum(a - 1, bit));
    }

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值