20250922 - 数位dp 总结

前言

我们可爱的人机测试,在规定的时间内没有一个人做出来(虽说只有10秒)!!!

被卡成安卓了!!!

数位 dp

众所不周知,周知不众所,dp 是一种比 dfs 要快的方法(记忆化当我没说),所以,在大规模的数据下,dp 的作用非常大。

看名字就知道,数位 dp,就是在上做 dp,把每一位数字拆开,算出贡献再合并结果!

怎么做呢?请听下回分解!


[ZJOI2010] 数字计数

此题一看,暴力,一看数据范围,呜呜呜,$a,b \le 10^{12} $。

暴力肯定不行了,怎么办?

考虑 dp[limit][l0][k] 为选了 k 个数,上限是 limit ,是否有前导零,那么,就可以愉快的记忆化搜索了!!!

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned ll
#define int ll // 我可"没有" #define int long long
#define db double
const int INF = 0x3f3f3f3f;
const int N = 13 + 7;
const int mod = 1e9 + 7;
ll dp[2][2][N];
ll power[N],suf[N],c[N];
inline ll dfs(const int &now,bool limit,bool l0,int k){
    if(k <= 0)
        return 0;
    if(dp[limit][l0][k] >= 0)
        return dp[limit][l0][k];
    dp[limit][l0][k] = 0;
    int l = limit ? c[k] : 9; // 判断上界
    for(int i = 0;i <= l;i++){
        dp[limit][l0][k] += dfs(now,limit && c[k] == i,l0 && i == 0,k-1);
        if(l0 && i == 0LL) // 如果有前导零,就进行下一次循环
            continue;
        if(i == now){
            if(limit && c[k] == i){
                dp[limit][l0][k] += suf[k-1] + 1;
            }else{
                dp[limit][l0][k] += power[k-1];
            } 
        }
    }
    return dp[limit][l0][k];
}
inline int query(int now,const int &pos){
    memset(dp,-1,sizeof(dp)); // 每次一定要清空!!!
    int len = 0; 
    while(now){
        c[++len] = now % 10;
        now /= 10;
        int i = len;
        suf[i] = suf[i-1] + power[len-1] * c[len]; // 求后缀
    }
    return dfs(pos,1,1,len);
} 
void init(){
    power[0] = 1;
    for(int i = 1;i <= 13;i++){
        power[i] = power[i-1] * 10LL;
    } 
    // 计算 10^k 是多少
    // 可以用快速幂,但cmy因此被抬走了!
}
ll a,b;
signed main(){
    init();
    scanf("%lld%lld",&a,&b);
    for(int i = 0;i <= 9;i++){
        printf("%lld ",query(b,i) - query(a-1,i)); // 前缀和!!!
    }
	return 0;
}

呃呃呃,太难了!!!

posted @ 2025-09-23 21:57  Ruochen_xia  阅读(25)  评论(0)    收藏  举报