数位DP经典例题

例题1:度的数量

题意

求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的B的整数次幂之和。

例如,设X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:

  • 17=24+20
  • 18=24+21
  • 20=24+22

题目链接:https://www.acwing.com/problem/content/1083/

数据范围

1XY2311
1K20
2B10

思路

首先,按照数位DP惯用套路,将问题转化为g(Y)g(X1),其中g(x)表示前x个数中满足要求的数字个数。

然后,我们可以将x转化为B进制,这样通过统计1的个数就能快速判断是否满足要求。

f(i,j)表示如果正在枚举第i位且已经有j1,答案有多少个(有limit的单算)。则f(i,j)=t=0upf(i1,j+t)

由于记忆化搜索版数位DP时间复杂度较高,因此考虑剪枝:在枚举的过程中,如果数量超过了K,则不继续搜索。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 35;

int l, r, k, b;
int f[N][N];
//f(i, j)表示如果正在枚举第i位且已经有j个b的整数次幂,答案有多少个
//最终答案为f(p - 1, 0)
int a[N];

int dfs(int p, int num, bool limit) //limit为1表示当前位上有限制,为0表示无限制
{
    if(p < 0) return num == k; //枚举完最后一位,判断是否为答案
    if(!limit && f[p][num] != -1) return f[p][num];
    int up = limit ? min(a[p], 1) : 1; //当前位的选择上限
    int ans = 0;
    for(int i = 0; i <= up; i ++) {
        if(num + i > k) continue;
        ans += dfs(p - 1, num + i, limit && i == a[p]);
    }
    if(!limit) f[p][num] = ans;
    return ans;
}

int solve(int x)
{
    int p = 0;
    while(x) {
        a[p ++] = x % b;
        x /= b;
    }
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, 1);
}

int main()
{
    cin >> l >> r >> k >> b;
    printf("%d\n", solve(r) - solve(l - 1));
    return 0;
}

例题2:数字游戏

题意

某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如123,446

现在大家决定玩一个游戏,指定一个整数闭区间[a,b],问这个区间内有多少个不降数。

题目链接:https://www.acwing.com/problem/content/1084/

数据范围

1ab2311

思路

在搜索的时候记录一下上一位是多少即可。

f(i,j)表示如果正在枚举第i位且上一位是j,答案有多少个(有limit的单算)。则f(i,j)=t=j+1upf(i1,t)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;

int l, r;
int f[N][N];
int a[N];

int dfs(int p, int pre, bool limit)
{
    if(p < 0) return 1;
    if(!limit && f[p][pre] != -1) return f[p][pre];
    int up = limit ? a[p] : 9;
    int ans = 0;
    for(int i = pre; i <= up; i ++) {
        ans += dfs(p - 1, i, limit && i == a[p]);
    }
    if(!limit) f[p][pre] = ans;
    return ans;
}

int solve(int x)
{
    int p = 0;
    while(x) {
        a[p ++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, 1);
}

int main()
{
    while(~scanf("%d%d", &l, &r)) printf("%d\n", solve(r) - solve(l - 1));
    return 0;
}

例题3:Windy数

题意

Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为2的正整数被称为 Windy 数。

Windy 想知道,在AB之间,包括AB,总共有多少个 Windy 数?

题目链接:https://www.acwing.com/problem/content/1085/

数据范围

1AB2×109

思路

这道题需要考虑前导零。考虑前导零的意思就是,前导零部分不需要满足题目中的限制条件。

与limit处理方法类似,是否是前导零用一个标记lead表示,然后单独去讨论前导零的情况即可。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 15;

char L[N], R[N];
int a[N];
ll f[N][N];

ll dfs(int p, int pre, bool lead, bool limit)
{
    if(p < 0) return 1;
    if(!limit && !lead && f[p][pre] != -1) return f[p][pre];
    int up = limit ? a[p] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; i ++) {
        if(lead) { //如果为前导零
            if(!i) ans += dfs(p - 1, 0, 1, limit && i == a[p]); //如果当前填0
            else ans += dfs(p - 1, i, 0, limit && i == a[p]); //如果当前不填0
        }
        else { //不是前导零
            if(abs(i - pre) >= 2) ans += dfs(p - 1, i, 0, limit && i == a[p]);
        }
    }
    if(!limit && !lead) f[p][pre] = ans;
    return ans;
}

ll solve(string s)
{
    int p = 0;
    int len = s.size();
    for(int i = len - 1; i >= 0; i --) a[p ++] = s[i] - '0';
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, 1, 1);
}

bool check(string s)
{
    int len = s.size();
    for(int i = 1; i < len; i ++) {
        int a = s[i] - '0', b = s[i - 1] - '0';
        if(abs(a - b) < 2) return false;
    }
    return true;
}

int main()
{
    cin >> L >> R;
    printf("%lld", solve(R) - solve(L) + check(L));
    return 0;
}

例题4:数字游戏II

题意

某人又命名了一种取模数,这种数字必须满足各位数字之和modN0

现在大家又要玩游戏了,指定一个整数闭区间[a.b],问这个区间内有多少个取模数。

题目链接:https://www.acwing.com/problem/content/1086/

数据范围

1a,b2311
1N<100

思路

这道题就是就是经典的计数DP拓展到了数位DP。

f(i,j)表示当前枚举到第i位且模数为j的情况下,方案数为多少。f(i,j)=t=0upf(i1,(j+t)%N)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15, M = 110;

int l, r, n;
int a[N];
int f[N][M];

int dfs(int p, int num, bool limit)
{
    if(p < 0) return !num;
    if(!limit && f[p][num] != -1) return f[p][num];
    int up = limit ? a[p] : 9;
    int ans = 0;
    for(int i = 0; i <= up; i ++) {
        ans += dfs(p - 1, (num + i) % n, limit && i == a[p]);
    }
    if(!limit) f[p][num] = ans;
    return ans;
}

int solve(int x)
{
    int p = 0;
    while(x) {
        a[p ++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, 1);
}

int main()
{
    while(cin >> l >> r >> n) printf("%d\n", solve(r) - solve(l - 1));
    return 0;
}

例题5:不要62

题意

[L,R]之间有多少数字里面没有462

题目链接:https://www.acwing.com/problem/content/1087/

数据范围

1LR109

思路

首先看没有4的情况,我们只需要在枚举当前位时,把4去掉即可。

对于没有62的情况,我们记录一个pre,如果pre为6且当前位为4,则continue

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;

int l, r;
int a[N];
int f[N][N];

int dfs(int p, int pre, bool limit)
{
    if(p < 0) return 1;
    if(!limit && f[p][pre] != -1) return f[p][pre];
    int up = limit ? a[p] : 9;
    int ans = 0;
    for(int i = 0; i <= up; i ++) {
        if(i == 4) continue;
        if(pre == 6 && i == 2) continue;
        ans += dfs(p - 1, i, limit && i == a[p]);
    }
    if(!limit) f[p][pre] = ans;
    return ans;
}

int solve(int x)
{
    int p = 0;
    while(x) {
        a[p ++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, 1);
}

int main()
{
    while(cin >> l >> r) {
        if(!l && !r) break;
        cout << solve(r) - solve(l - 1) << endl;
    }
    return 0;
}

例题6:计数问题

题意

给定两个整数ab,求ab之间的所有数字中09的出现次数。

题目链接:https://www.acwing.com/problem/content/340/

数据范围

0<a,b<108

思路

这道题需要统计10个量,因此我们可以跑10次数位DP,分别统计每个数字的出现个数。

由于这道题前导零是不算在内的,因此需要考虑前导零的情况。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;

int l, r;
int a[N];
int f[N][N];

int dfs(int p, int num, int x, bool lead, bool limit)
{
    if(p < 0) return num;
    if(!limit && !lead && f[p][num] != -1) return f[p][num];
    int up = limit ? a[p] : 9;
    int ans = 0;
    for(int i = 0; i <= up; i ++) {
        if(lead) {
            if(!i) ans += dfs(p - 1, num, x, lead, limit && i == a[p]);
            else ans += dfs(p - 1, num + (x == i), x, 0, limit && i == a[p]);
        }
        else ans += dfs(p - 1, num + (x == i), x, lead, limit && i == a[p]);
    }
    if(!limit && !lead) f[p][num] = ans;
    return ans;
}

int solve(int x, int t)
{
    int p = 0;
    while(x) {
        a[p ++] = x % 10;
        x /= 10;
    }
    memset(f, -1, sizeof f);
    return dfs(p - 1, 0, t, 1, 1);
}

int main()
{
    while(cin >> l >> r) {
        if(!l && !r) continue;
        if(l > r) swap(l, r);
        for(int i = 0; i < 10; i ++) {
            printf("%d ", solve(r, i) - solve(l - 1, i));
        }
        printf("\n");
    }
    return 0;
}
posted @   pbc的成长之路  阅读(172)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示