数位DP

数位DP

与数位有关的 DP.  

一般来说,无脑记忆化搜索就完事了.  

直接递推不仅细节繁琐,而且还不好想.   

记忆化搜索的时候要记录:
1. 当前数位.

2. 是否顶着上界.

3. 是否有前导零.

4. 计数器(为了满足题目中的条件)  

 

luoguP4317 花神的数论题

非常经典的数位 DP.

注意如果想要采取快速幂取模的话在 DP 的时候要开 $\mathrm{long }$ $\mathrm{long}$.  

不然得用欧拉定理什么的.  

记忆化搜索部分:

// 从高位向低位开始 dp 
ll dfs(int cur,int d,int one,int tot) {
    if(cur == 0) {
        return (one == tot);  
    }
    if(f[cur][d][one][tot] != -1) 
        return f[cur][d][one][tot]; 
    int lim = d ? a[cur] : 1;  
    f[cur][d][one][tot] = 0 ; 
    for(int i = 0 ; i <= lim ; ++ i) {
        f[cur][d][one][tot] += dfs(cur - 1, d && (i == lim), one + (i == 1), tot);  
    }
    return f[cur][d][one][tot]; 
}

完整代码:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 60
#define ll long long
#define pb push_back  
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
const int mod=10000007;
ll f[N][2][N][N], ans[N];
int a[N], cnt;  
int qpow(int x,ll y) {
    int tmp=1; 
    for(;y;y>>=1,x=(ll)x*x%mod) {
        if(y&1) tmp=(ll)tmp*x%mod; 
    }
    return tmp; 
}
// 从高位向低位开始 dp 
ll dfs(int cur,int d,int one,int tot) {
    if(cur == 0) {
        return (one == tot);  
    }
    if(f[cur][d][one][tot] != -1) 
        return f[cur][d][one][tot]; 
    int lim = d ? a[cur] : 1;  
    f[cur][d][one][tot] = 0 ; 
    for(int i = 0 ; i <= lim ; ++ i) {
        f[cur][d][one][tot] += dfs(cur - 1, d && (i == lim), one + (i == 1), tot);  
    }
    return f[cur][d][one][tot]; 
}
int main() {
    // setIO("input"); 
    memset(f, -1, sizeof(f)); 
    ll n; 
    scanf("%lld",&n); 
    while(n) {
        a[++cnt] = n & 1; 
        n >>= 1; 
    }
    for(int i=1;i<=50;++i) {
        ans[i] = dfs(cnt, 1, 0, i); 
    }
    int re = 1; 
    for(int i=1;i<=50;++i) {
        re = (ll)re * qpow(i, ans[i]) % mod; 
    }
    printf("%d\n",re); 
    return 0; 
}

  

luoguP2657 [SCOI2009] windy 数

同样也是十分经典的数位 DP.  

不过在这道题中就要记录是否有前导零.  

因为如果有前导零的话第一位填入什么数字是没有限制的.  

要注意每次算的时候要将数组什么的清空. 

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N  12 
#define ll long long
#define pb push_back  
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
int f[N][N][2][2], a[N], cnt;  
// 从高位向低位. 
int dfs(int cur, int num, int d, int l) {
    if(cur == 0) {
        return 1; 
    }
    if(f[cur][num][d][l] != -1) {
        return f[cur][num][d][l]; 
    }
    int lim = d ? a[cur] : 9;  
    int re = 0; 
    for(int i = 0 ; i <= lim ; ++ i) {
        if(!l) {
            // 无前导 0.
            // 即前面至少有一个数不是 0.  
            if(abs(i - num) >= 2) 
                re += dfs(cur - 1, i, d && (i == lim), l);   
        }
        else {
            // 含有前导 0.  
            re += dfs(cur - 1, i, d && (i == lim), l && (i == 0));
        }
    }
    return f[cur][num][d][l] = re;  
} 
int solve(int x) {
    cnt = 0 ; 
    memset(f, -1, sizeof(f)); 
    while(x) {
        a[++cnt] = x % 10; 
        x /= 10; 
    }

    // 处理完毕 a.  
    // 1: 含有前导 0.   
    return dfs(cnt, 0, 1, 1);  
}       
int main() {
    // setIO("input");  
    int L, R; 
    scanf("%d%d",&L,&R);   
    printf("%d\n",solve(R) - solve(L - 1));  
    return 0; 
}

  

luoguP2602 [ZJOI2010]数字计数

令 $\mathrm{f(i,d,l,num)}$ 表示位数,是否顶上界,是否有前导0,统计数字.  

令 $\mathrm{g(i,d,l)}$ 表示位数,是否顶上界,是否有前导 0 的方案数.  

我们要求 $\mathrm{f}$, 但在统计的时候要用到 $\mathrm{g}$.  

先记忆化搜索出所有的 $\mathrm{g}$, 再来统计 $\mathrm{f}$ 即可.   

#include <cstdio> 
#include <cstring>
#include <vector>
#include <algorithm>
#define pb push_back
#define ll long long 
#define N 20 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
// f 数组:
// g 数组:方案数.
ll f[N][2][2][10], g[N][2][2];  
int a[N], cnt;   
// 计算 g 数组,即多少个数字满足这个条件.
// 即 cur 位,是否顶上界,是否有前导 0. 
ll dfs1(int cur,int d,int l) {
    if(cur == 0) {
        return g[cur][d][l] = 1;        
    }
    if(g[cur][d][l] != -1) {
        return g[cur][d][l]; 
    }
    g[cur][d][l] = 0; 
    ll re = 0;  
    int lim = d ? a[cur] : 9;   
    for(int i = 0 ; i <= lim ; ++ i) {
        re += dfs1(cur - 1, d && (i == lim), l && (i == 0));       
    }
    return g[cur][d][l] = re; 
}
ll dfs2(int cur,int d,int l,int num) {
    if(cur == 0) {
        return 0; 
    }
    if(f[cur][d][l][num] != -1 ) 
        return f[cur][d][l][num];  
    int lim = d ? a[cur] : 9;    
    f[cur][d][l][num] = 0;  
    ll re = 0; 
    for(int i = 0 ; i <= lim ; ++ i) {
        ll det; 
        if(l == 0) {
            // 无前导 0.  
            det = dfs2(cur-1, d&&(i==lim),0,num) + (i==num)*g[cur-1][d&&(i==lim)][0];      
        }
        else {
            if(num) {
                det = dfs2(cur-1,d&&(i==lim),l&&(i==0),num)+(i==num)*g[cur-1][d&&(i==lim)][l&&(i==0)];  
            }
            else {
                det = dfs2(cur-1,d&&(i==lim),l&&(i==0),num);  
            }
        }
        re += det; 
    }
    return f[cur][d][l][num] = re; 
}
ll solve(ll x, int num) {
    memset(f, -1, sizeof(f));  
    memset(g, -1, sizeof(g)); 
    cnt = 0; 
    while(x) {
        a[++cnt] = x % 10;  
        x /= 10; 
    }
    // 初始:有前导 0,且有上界.  
    dfs1(cnt, 1, 1);  
    return dfs2(cnt,1,1,num);
}
int main() {
    // setIO("input"); 
    ll L, R; 
    scanf("%lld%lld",&L,&R);  
    for(int i=0;i<=9;++i) {
        printf("%lld ", solve(R, i) - solve(L - 1, i));   
    }
    return 0; 
}

  

luoguP4124 [CQOI2016]手机号码

细节较前两道题多了一点. 

由于题目中规定了 $\mathrm{11}$ 位数,所以可以先枚举最高位.  

这样就无需考虑前导 $0$ 的问题了.  

剩下的就是大力进行数位 $\mathrm{DP}$ 了.  

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N  13 
#define pb push_back 
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
int cnt, a[N]; 
/* 
    j:   cur + 1 位. 
    k:   cur + 2 位.
    d:   是否顶着上界.  
    sta: 4/8 的状态.  
    o:   是否有 3 个相邻的状态. 
    cur : 当前还没有装呢. 
 */  
ll f[N][N][N][2][N][2];   
ll dfs(int cur,int j,int k,int d,int sta,int o) {
    if(cur == 0) {
        return (o == 1); 
    }
    if(f[cur][j][k][d][sta][o] != -1) {
        return f[cur][j][k][d][sta][o]; 
    }
    f[cur][j][k][d][sta][o] = 0; 
    ll re = 0;   
    int lim = d ? a[cur] : 9;  
    // 向下一个放置 nex.  
    for(int nex = 0; nex <= lim ; ++ nex) {
        if(sta == 4 && nex == 8) continue;  
        if(sta == 8 && nex == 4) continue;   
        int pp = 0; 
        if(sta == 4 || nex == 4) pp = 4; 
        if(sta == 8 || nex == 8) pp = 8;  
        re += dfs(cur - 1, nex, j, d && (nex == lim), pp, o || (j == k && k == nex));    
    }
    return f[cur][j][k][d][sta][o] = re;  
}
ll calc(ll x) {
    cnt = 0 ; 
    while(x) {
        a[++cnt] = x % 10; 
        x /= 10; 
    }
    ll re = 0ll; 
    for(int i = 1; i <= a[cnt] ; ++ i) {
        int j = 0;  
        if(i == 4) j = 4; 
        if(i == 8) j = 8; 
        memset(f, -1, sizeof(f)); 
        re += dfs(10, i, 0, (i == a[cnt]), j, 0);  
    }
    return re;  
}
int main() {
    // setIO("input");  
    ll L, R; 
    scanf("%lld%lld",&L,&R);  
    if(L == (ll)1e10) {
        printf("%lld\n",calc(R)); 
    }
    else {
        printf("%lld\n",calc(R) - calc(L - 1)); 
    }
    return 0; 
}

  

Round Numbers 圆环数

来源:bzoj1662. [Usaco2006 Nov]Round Numbers 圆环数

和前面的题一样,用记忆化搜索解决.  

数位 DP 的题用记忆化搜索不仅简单,而且非常好理解.  

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 35  
#define ll long long 
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
int a[N], cnt, f[N][2][N][N][2];  
/*
    cur: 当前位.  
    d:   是否顶着上界.  
    zero: 0 的个数.
    one:  1 的个数 
    l :  是否有前导 0.       
*/
int dfs(int cur, int d, int zero, int one, int l) {
    if(cur == 0) {
        return zero >= one; 
    }
    if(f[cur][d][zero][one][l] != -1) {
        return f[cur][d][zero][one][l]; 
    }
    f[cur][d][zero][one][l] = 0;  
    int re = 0;   
    int lim = d ? a[cur] : 1; 
    for(int i = 0; i <= lim ; ++ i) {
        if(l == 0) {
            // 并没有前导 0. 
            re += dfs(cur - 1, d && (i == lim), zero + (i == 0), one + (i == 1), 0);   
        }
        else {
            re += dfs(cur - 1, d && (i == lim), zero, one + (i == 1), l && (i == 0));   
        }
    }
    return f[cur][d][zero][one][l] = re; 
}
int solve(int x) {
    cnt = 0; 
    while(x) {
        a[++ cnt] = x & 1;  
        x >>= 1; 
    }
    memset(f, -1, sizeof(f));  
    return dfs(cnt, 1, 0, 0, 1);  
}
int main() {
    // setIO("input"); 
    int L, R; 
    scanf("%d%d",&L,&R); 
    //printf("%d\n", solve(R)); 
    printf("%d\n", solve(R) - solve(L - 1)); 
    return 0; 
}

  

 

posted @ 2021-09-23 14:14  guangheli  阅读(50)  评论(0)    收藏  举报