[数位DP][AHOI2009] Luogu P4127 同类分布


# include <iostream>
# include <cstdio>
# include <cstring>
# define LL long long
# define MAXN 22

using namespace std;

int sum, a[MAXN]; 
// sum 记录各个位数的和
// a[0]记录当前数的总位数, 后面依次记录位
LL f[MAXN][MAXN*9][MAXN*9];
// f[dep][cur][mod] -> 当前 dp 到了第 dep 位, 现在该数的各位之和为 cur, mod 为 % 当前情况的sum值

LL DFS(int dep, int cur, int mod, bool eq){
    if(cur > sum) return 0; // 当总和比当前统计的情况的和还大说明不符合当前的情况
    if(!dep) return mod == 0 && cur == sum; // 如果当前各位数之和与要求的各位数之和相等, 且 mod=0, 就是一个符合条件的答案
    if((!eq)&&(~f[dep][cur][mod])) return f[dep][cur][mod]; // 如果当前不是恰好是答案的询问就可以套记忆化搜索
    // 注意记忆化搜索得到的值都是按照每位的范围为 0~9 计算的, 拿来更新 eq 为真的值就 boom 了

    // 若当前的值没有超出边界而且第一次被搜索到
    int lim = (eq? a[dep] : 9); // 确定下一位可以选择的最大值

    LL ans = 0;
    for(int i = 0; i <= lim; i++) // 搜索下一位的数位情况, 注意下一位数位从 0 开始
        ans += DFS(dep-1, cur+i, (mod*10+i)%sum, eq&&(i==lim));
    
    if(!eq) // 当前的答案可以用于更新记忆化搜索的值
        f[dep][cur][mod] = ans;
    return ans;
}
// dep -> 当前所在的位数
// cur -> 当前的数位之和
// mod -> 当前的数 %sum 的值
// eq  -> 记录上一位填入的数是否和 a[] 中的数相等

LL DP(LL x){
    a[0] = 0;

    for(LL i = x; i; i/=10)
        a[++a[0]] = i%10; // 预处理 a[i] 数组
    
    LL ans = 0;
    for(int i = 1; i <= a[0]*9; i++){ // 各个位数之和一定不超过 位数*9
        sum = i; // 统计位数和为 i 的情况
        memset(f, -1, sizeof(f)); // 初始化 f 为(按位)全 1
        ans += DFS(a[0], 0, 0, 1); // DFS 过程
    }
    return ans;    
}

int main(){
    LL l, r;
    cin>>l>>r;
    cout<<DP(r) - DP(l-1);
    return 0;
}
posted @ 2020-07-10 17:59  ChPu437  阅读(87)  评论(0编辑  收藏  举报