OVSolitario-io

导航

数位DP:拆位分别进行DP,优化掉大范围

数位DP:

最重要一步:分情况讨论

板子:

点击查看代码
int dfs(int pos, int lim, ...) {
    if(pos == 0) {
        ...
        return ...
    }
    if((!lim) && dp[pos][...] != -1) return dp[pos][...];
    int ans = 0;
    int ed = lim ? bit[pos] : 9;
    for(int i = 0; i <= ed; ++ i) {
        ans += dfs(pos - 1, lim && i == ed, ...);
    }
    if(!lim) dp[pos][...] = ans;
    return ans;
}

hud3709 Balanced Number
截屏2025-10-17 11.10.17

力距:e.g. 123,以2为支点,则1~2距离为1,权值为1,则为1,2~3距离(有向距离)为-1,则为-3,力矩则为1 + -3 = -2

数位DP将不能接受的数进行拆位优化

截屏2025-10-17 11.19.02

思路:枚举支点的all可能位置,其中对于10位数,假设已有后面9位答案,那么再加上10种状态加入即当前答案

使用记忆化搜索:

点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
ll dp[20][20][1500];
ll bit[20];

ll dfs(ll pos, ll o, ll sum, ll fl) {//o支点,sum力矩和,fl表当前为是否可for到9
    if(pos == 0) {//最终sum=0即答案
        if(sum == 0) return 1;
        else return 0;
    }
    if(sum < 0) return 0;
    if((!fl) && dp[pos][o][sum] != -1) return dp[pos][o][sum];//记忆化
    //具体做法:
    ll ans = 0;
    ll ed = fl ? bit[pos] : 9;
    for(ll i = 0; i <= ed; ++ i) {
        //支点不改变,当可执行时将对应新位上的值加入
        ans += dfs(pos - 1, o, sum + (pos - o) * i, fl && i == ed);
    }
    if(!fl) dp[pos][o][sum] = ans;
    return ans;
}

ll ca(ll x) {
    if(x < 0) return 0;
    if(x == 0) return 1;
    ll len = 0;
    while(x > 0) {//拆分
        bit[++ len] = x % 10;
        x /= 10;
    }
    ll ans = 0;
    for(ll i = len; i > 0; -- i) {//for长度:来确定位数
        ans += dfs(len, i, 0, 1);
    }
    ans -= (len - 1);
    return ans;
}
int main() {
    memset(dp, -1, sizeof dp);
    int t;
    ll l, r;
    scanf("%d", &t);
    while(t -- ) {
        scanf("%lld%lld", &l, & r);
        printf("%ld\n", ca(r) - ca(l - 1));
    }
    return 0;
}

对于数最高位,显然若最高位i < 9则显然无法for到9

hdu3652

需要额外存上一位的状态,不断记录sum,判定%13是否整除

截屏2025-10-19 08.43.04
对每一位枚举pos可能,此时即sum可直接判断|13,记录flag,当结束时只需要看num是否为|13且flag = 1


对于区间答案,常用的是转换为前缀答案(相减)

计数问题
对于[a,b]中x出现的次数->转换为实现count(n,x):1~n中x出现的次数,以此类前缀和利用:[a,b] = count(b, x) - count(a - 1, x)

对于1~n中x出现次数 <=> 1出现在每一位的次数再相加

例如:
对于n = abcdedf,存在1 <= xxx1yyy <= n来说
分情况讨论:
当枚举x>0时:

  • (1)xxx = 000 ~ abc-1: 则有yyy任取000~999,方案数=abc * 1000
  • (2)xxx = abc,此时存在3种情况
    • (2.1)d < x,因前三位固定,abc1yyy > abc0efg(不合法),方案数 = 0
    • (2.2)d = x,(此时前4位达到上限)yyy可取000~efg,方案数 = efg + 1
    • (2.3)d > x,此时yyy任取,1000种

当x = 0时:

对于条件(2)无影响,只影响(1)(其存在000这种前导零问题)
如:7位数字,当前3位000第4置入0,构成0000xxx(不合法,会将前导零都删掉)

所以对于x=0情况,(1)从001开始枚举

对符合情况((1)+(2)某一种)方案数进行累加,即可求出1在任意位上出现情况(同理扩展到x在任意位情况)

此时仍需考虑边界

  • 1在最高位: 则只考虑情况(2)即可
  • 枚举0时: 若0出现在第4位,则前3位不能是000(不合法,不存在前导零),要至少从001开始

对于(1):xxxi(第i个)yyy的方案数 = xxx(固定) * 10^i,现实函数:

int get(vector<int> num, int l, int r)//前面位构成数字
{
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

int power10(int x)//求10^i
{
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

发现对于最高位:显然不能判断0~9,而是1~最高位当前数a(高位无),所以要特判断情况(1)不等于n-1

且又有:修正多计算出的前导零的个数

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
/*

001~abc-1, 999

abc
    1. num[i] < x, 0
    2. num[i] == x, 0~efg
    3. num[i] > x, 0~999

*/

int get(vector<int> num, int l, int r)//前面位构成数字
{
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

int power10(int x)//求10^i
{
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)//1~n中x出现次数
{
    if (!n) return 0;

    vector<int> num;
    while (n)
    {
        num.push_back(n % 10);//取出每一位
        n /= 10;
    }
    n = num.size();//n此时变为位数

    int res = 0;//次数
    for (int i = n - 1 - !x; i >= 0; i -- )//若最高位为0,则从次高位开始
    {
        //第一类:除最高位看情况(1)
        if (i < n - 1)
        {
            res += get(num, n - 1, i + 1) * power10(i);
            if (!x) res -= power10(i);//若为0,修正前导零导致的多算
        }
        //第二类:最高位非循环0~9,有限制0~值a,看情况(2)
        if (num[i] == x) res += get(num, i - 1, 0) + 1;//(2.2)
        else if (num[i] > x) res += power10(i);//(2.3)
    }

    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);

        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }

    return 0;
}

posted on 2025-10-09 21:17  TBeauty  阅读(4)  评论(0)    收藏  举报