1. 程式人生 > 實用技巧 >[數位DP][AHOI2009] Luogu P4127 同類分佈

[數位DP][AHOI2009] Luogu P4127 同類分佈

最後開long long過了, 心累, 摸了, 明天再寫


# 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;
}