To boldly go where no one has gone before.

【学习笔记】数位dp

2019-03-17 22:53:08


数位$\textbf{dp}$的特征是:要求计算$[L,R]$区间内符合要求的数

(例题:P2657

用$f[i][j]$保存从前往后考虑到第$i$个数位,前缀为$j$情况下的答案。很明显,我们要枚举数位来进行计算。

核心转移方程是:

$$f(i,pre,equal)=\sum_{i=1}^{up}f(i+1,k,equal \ \& \ (i==limit))$$ 在这道题当中,$k$需要满足$\left| k-i\right|\ge2$。

需要注意的是$\quad equal \ and \ i==limit\quad$这句话,如果之前的数位都被限制,并且当前数位也被限制,那么下一个数位也会受限制,否则不会受限制。因为位数相同的情况下,如果一个数$A$比另一个数$B$小,那么从最高位开始比较,第一个不相同的数位$A$一定比$B$小

还有这句话:

if(pre == 11 && i == 0)
    ans += dfs(x - 1, 11, equal && (i == limit));
else
    ans += dfs(x - 1, i, equal && (i == limit));

也非常坑,需要注意的是当$pre=11$且$i=0$的时候,下一次搜索的$equal$才等于$11$。这也就是处理前导$0$,只要当前位数之前存在一个数位,那么$limit$就不可能为$11$,因此在枚举数位枚举到$0$的时候,如果这个$0$依然是前导$0$,那么就将$limit=11$继续传到下去,然后再记忆化搜索即可。

其他的根据实际情况写就可以了。


完整代码

#include<bits/stdc++.h>
using namespace std;

int f[15][15];
vector<int> digit;

inline long long dfs(int x, int pre, int equal) {
    if(!x)
        return 1;
    if(!equal && ~f[x][pre])
        return f[x][pre];
    int limit = equal ? digit[x] : 9;
    long long ans = 0;
    for(register int i = 0; i <= limit; i++)  {
        if(abs(i - pre) < 2)
            continue;
        if(pre == 11 && i == 0)
            ans += dfs(x - 1, 11, equal && (i == limit));
        else
            ans += dfs(x - 1, i, equal && (i == limit));
    }
    if(!equal)
        f[x][pre] = ans;
    return ans;
}

inline long long solve(int x) {
    memset(f, -1, sizeof(f));
    digit.clear();
    digit.push_back(-1);
    while(x) {
        digit.push_back(x % 10);
        x /= 10;
    }
    return dfs(digit.size() - 1, 11, 1);
}

int main() {
    int a, b;
    cin >> a >> b;
    cout << solve(b) - solve(a - 1);
    return 0;
}

例题 P2602

搜索的时候需要记录当前是否含有前导零,且$f(x,\,pre,\,equal,\,has\_zero)$表示的是意思是 “当前在第$x$位上,前缀状态数为$pre$,是否与当前数字保持相等,是否含有前导零” 的情况下的状态数。

注意!

判断$x$不是$-1$的方法是$\sim x$,不能打$!\sim x$

被坑很多次了!

#include<bits/stdc++.h>
using namespace std;

long long f[15][15][2][2];
int digit[15], len = 0;

inline long long dfs(int x, int pre, int now_d, bool equal, bool has_zero) {
    if(x <= 0)
        return pre;
    if(~f[x][pre][equal][has_zero])
        return f[x][pre][equal][has_zero];
    long long res = 0;
    int limit = equal ? digit[x] : 9;
    for(register int i = 0; i <= limit; i++)
        res += dfs(x-1, pre+((!has_zero||i) && (i==now_d)), now_d, equal&&(i==limit), has_zero&&(i==0));
    return f[x][pre][equal][has_zero] = res;
}

inline long long solve(long long x, int now_d) {
    memset(f, -1, sizeof(f));
    for(len = 0; x; x /= 10)
        digit[++len] = x % 10;
    return dfs(len, 0, now_d, 1, 1);
}

int main() {
    long long x, y;
    scanf("%lli %lli", &x, &y);
    for(register int i = 0; i <= 9; i++)
        printf("%lli ", solve(y, i) - solve(x-1, i));
}