0%

数位dp汇总

最近发现数位dp其实挺常见的,这里会陆陆续续把自己见到的数位dp放在这里一起总结。

1 - G - Palindrome Function

定义函数$f(n,k)=k$,如果n在k进制是回文数。否则$f(n,k)=1$

多组询问,给定$L,R,l,r$,求
$$
\sum_{i=L}^R \sum_{j=l}^r f(i,j)
$$
其实基本可以上数位dp的板子,设dp[base][pos][mxlen]是在base进制,枚举到pos位,得到的数的最大长度是mxlen的条件下的回文数个数,即可解决。

值得注意的是,对于询问组数较少的数位dp,最好躲记录一些状态,比如dp[base][pos][mxlen][lead][lim]的形式。 这样可以在一次记忆化搜索时,避免因为边界条件而重新搜索。但是,这样的代价是我们必须在每一次搜索前将dp清为-1,因为此时dp的值与计算的数密切相关。如果是多组询问,我们自然希望之前的搜索结果能尽量发挥作用,所以我们选择少记几个状态,让dp的意义与之前搜索的边界条件无关,使得dp更加通用,避免了每次memset的麻烦。

#参考代码

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline void read(int &x) {
    char ch = getchar(); x = 0;
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch))
        x = x * 10 + (ch ^ '0'), ch = getchar();
}
int d[40], tmp[40];
int dp[40][40][40], base;
int dfs(int pos, int mxlen, bool lead, bool lim) {
    if(pos == -1) return 1;
    if(dp[base][pos][mxlen] != -1 && !lead && !lim) return dp[base][pos][mxlen];
    int up = lim ? d[pos] : base - 1;
    int res = 0;
    for(int i = 0; i <= up; ++i) {
        tmp[pos] = i;
        if(lead) // 有前导零,则要注意数的长度可能会变小
            res += dfs(pos - 1, (mxlen - (i == 0)), i == 0, lim && i == up);
        else {
            if(pos >= mxlen / 2) // 前一半数随便填
                res += dfs(pos - 1, mxlen, 0, lim && i == up);
            else if(i == tmp[mxlen - pos - 1]) // 后一半要和前一半对应
                res += dfs(pos - 1, mxlen, 0, lim && i == up);
        }
    } 
    return ((lim || lead) ? res : dp[base][pos][mxlen] = res); // 只有非特殊情况才能保留这个值
}
ll solve(int x, int b) {
    int len = 0; base = b;
    // memset(dp, -1, sizeof dp); orz 不要在这里搞
    while(x) {
        d[len++] = x % base;
        x /= base;
    }
    return dfs(len - 1, len, 1, 1);
}
int main() {
    int t;
    read(t);
    memset(dp, -1, sizeof dp);
    for(int cas = 1; cas <= t; ++cas) {
        int L, R, l, r;
        read(L), read(R), read(l), read(r);
        ll ans = 0;
        for(int i = l; i <= r; ++i) {
            ll res = solve(R, i) - solve(L - 1, i);
            ans += res * base + (R - L + 1 - res);
        }
        printf("Case #%d: %lld\n", cas, ans);
    }
    return 0;
}