数位dp题集

题集见大佬博客

不要62

入门题,检验刚才自己有没有看懂

注意一些细节。

的确挺套路的

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 15;
int a[MAXN], dp[MAXN][MAXN][MAXN], len; //dp数组记录除了lead和limit以外其他的东西 

int dfs(int pos, int pre, int lead, int sum, int limit)
{
    if(pos > len) return sum;
    if(dp[pos][pre][sum] != -1 && !limit && !lead) return dp[pos][pre][sum]; //记忆化的时候记得limit和lead 
    int l = limit ? a[len-pos+1] : 9, res = 0; //注意是倒序存的,所以是len-pos+1
    _for(i, 0, l)
    {
        if(!i && lead) res += dfs(pos + 1, pre, lead, sum, limit && (i == l)); //先看是不是前导0 
        else if(i && lead) res += dfs(pos + 1, i, 0, sum | (i == 4), limit && (i == l)); //看是不是第一位 
        else res += dfs(pos + 1, i, 0, sum | (i == 4) | (pre == 6 && i == 2), limit && (i == l)); //正式处理 
    }
    return (!limit && !lead) ? dp[pos][pre][sum] = res : res; //记忆化的时候记得limit和lead 
}

int part(int x)
{
    if(x < 0) return 0; //0的处理 
    memset(dp, -1, sizeof(dp));
    len = 0; int t = x; 
    for(; x; x /= 10) a[++len] = x % 10;
    return t - dfs(1, 0, 1, 0, 1);
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m))
    {
        if(n == 0 && m == 0) break;
        printf("%d
", part(m) - part(n - 1));
    }
    return 0;
}

P2657 [SCOI2009]windy数

继续套路

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 15;
int a[MAXN], dp[MAXN][MAXN][MAXN], len;

int dfs(int pos, int pre, int ans, int lead, int limit)
{
    if(pos > len) return ans;
    if(dp[pos][pre][ans] != -1 && (!limit && !lead)) return dp[pos][pre][ans];
    int l = limit ? a[len-pos+1] : 9, res = 0;
    _for(i, 0, l)
    {
        if(!i && lead) res += dfs(pos + 1, pre, ans, lead, limit & (i == l));
        else if(i && lead) res += dfs(pos + 1, i, ans, 0, limit & (i == l));
        else res += dfs(pos + 1, i, ans & (abs(i - pre) >= 2), 0, limit & (i == l));
    }
    return (!limit && !lead) ? dp[pos][pre][ans] = res : res;
}

int part(int x)
{
    if(x == 0) return 1;
    memset(dp, -1, sizeof(dp));
    len = 0; 
    for(; x; x /= 10) a[++len] = x % 10;
    return dfs(1, 0, 1, 1, 1);
}

int main()
{
    int a, b;
    scanf("%d%d", &a, &b); 
    printf("%d
", part(b) - part(a - 1));
    return 0;
}

P2602 [ZJOI2010]数字计数

第一次这么轻松做出紫题

一样套模板,爽啊

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 20;
const int MAXM = 1e6 + 10;
ll dp[MAXN][MAXN];
int a[MAXN], len;

ll dfs(int pos, int ans, int lead, int limit, int key)
{
    if(pos > len) return ans;
    if(dp[pos][ans] != -1 && (!lead && !limit)) return dp[pos][ans];
    int l = limit ? a[len-pos+1] : 9; ll res = 0;
    _for(i, 0, l)
    {
        if(!i && lead) res += dfs(pos + 1, ans, lead, limit && (i == l), key);
        else res += dfs(pos + 1, ans + (i == key), 0, limit && (i == l), key);
    }
    return (!lead && !limit) ? dp[pos][ans] = res : res;
}

ll part(ll x, int i)
{
    memset(dp, -1, sizeof(dp));
    len = 0;
    for(; x > 0; x /= 10) a[++len] = x % 10;
    return dfs(1, 0, 1, 1, i);
}

inline ll work(ll a, ll b, int i)
{
    return a ? part(b, i) - part(a - 1, i) : part(b, i) - part(a, i) + (i == 0);
}

int main()
{
    ll a, b;
    scanf("%lld%lld", &a, &b);
    _for(i, 0, 9) 
        printf("%lld%c", work(a, b, i), i == 9 ? '
' : ' ');
    return 0;
}

P3413 SAC#1 - 萌数

注意数字很大,要用字符串存储

然后就没啥了,又独立做出紫题

#include<bits/stdc++.h>
#define add(a, b) a = (a + b) % mod
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 12;
const int MAXM = 1e3 + 10;
const int mod = 1e9 + 7;

int dp[MAXM][MAXN][MAXN][2];
int a[MAXM], len;
char s1[MAXM], s2[MAXM];

int dfs(int pos, int pre, int ppre, int ans, int lead, int limit)
{
    if(pos > len) return ans;
    if(dp[pos][pre][ppre][ans] != -1 && (!lead && !limit)) return dp[pos][pre][ppre][ans];
    int l = limit ? a[len-pos+1] : 9; int res = 0;
    _for(i, 0, l)
    {
        if(!i && lead) add(res, dfs(pos + 1, pre, ppre, ans, lead, limit && (i == l)));
        else if(i && lead) add(res, dfs(pos + 1, i, pre, ans, 0, limit && (i == l)));
        else add(res, dfs(pos + 1, i, pre, ans | (i == pre) | (i == ppre), 0, limit && (i == l)));
    }
    return (!lead && !limit) ? dp[pos][pre][ppre][ans] = res : res;
}

int part(char* s)
{
    memset(dp, -1, sizeof(dp));
    len = strlen(s + 1);
    _for(i, 1, len) a[i] = s[len - i + 1] - '0';
    return dfs(1, -1, -1, 0, 1, 1);
}

int judge(char* s)
{
    _for(i, 2, strlen(s + 1))
        if(s[i] == s[i-2] || s[i] == s[i-1])
            return 1;
    return 0;
}

int main()
{
    scanf("%s%s", s1 + 1, s2 + 1);
    if(s1[1] == 0 && strlen(s1 + 1) == 1) printf("%d
", (part(s2) - part(s1) + mod) % mod);
    else printf("%d
", (part(s2) - part(s1) + mod + judge(s1)) % mod);
    return 0;
}

P4127 [AHOI2009]同类分布

一开始想的时候当前的数肯定是不能记到答案里的

当然如果知道模数,一直取模就可以限制范围,就很好了

但问题是我们并不知道模数,这就比较尴尬了

就一直卡在这

然而正解非常暴力,但却是对的。

既然不知道模数,那就枚举模数

最后判断一下数字和是不是当前模数且能否整除就好了。

我为什么没想到呢?

注意数位dp要开long long

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 20;
ll dp[MAXN][MAXN*10][MAXN*10];
int a[MAXN], len, mod;

ll dfs(int pos, int num, int state, int lead, int limit)
{
    if(pos > len) return num == mod && state == 0;
    if(dp[pos][num][state] != -1 && (!lead && !limit)) return dp[pos][num][state];
    int l = limit ? a[len-pos+1] : 9; ll res = 0;
    _for(i, 0, l)
    {
        if(!i && lead) res += dfs(pos + 1, num, state, lead, limit && (i == l));
        else if(i && lead) res += dfs(pos + 1, i, i % mod, 0, limit && (i == l));
        else res += dfs(pos + 1, num + i, (state * 10 + i) % mod, 0, limit && (i == l));
    }
    return (!lead && !limit) ? dp[pos][num][state] = res : res;
}

ll part(ll x)
{
    len = 0;
    for(; x; x /= 10) a[++len] = x % 10;
    ll res = 0;
    for(mod = 1; mod <= len * 9; mod++)
    {
        memset(dp, -1, sizeof(dp));
        res += dfs(1, 0, 0, 1, 1);
    }
    return res;
}

int main()
{
    ll a, b;
    scanf("%lld%lld", &a, &b);
    printf("%lld
", !a ? part(b) - part(a) : part(b) - part(a - 1));
    return 0;
}

P4317 花神的数论题

用二进制统计就好了

不过很奇怪的是一开始我数组开的大小是50

因为用程序输出1e15最多的位数是47

但是交上去会WA一个点

改成51就过了

以后只要空间剩余多,就多开一些吧,程序中有些神奇的地方可能会比理论上最大值超出一些

#include<bits/stdc++.h>
#define mul(a, b) a = (a * b) % mod
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 51;
const int mod = 10000007;
ll dp[MAXN][MAXN];
int a[MAXN], len;

ll dfs(int pos, int ans, int lead, int limit)
{
    if(pos > len) return max(1, ans);
    if(dp[pos][ans] != -1 && (!limit && !lead)) return dp[pos][ans];
    int l = limit ? a[len-pos+1] : 1; ll res = 1;
    _for(i, 0, l)
    {
        if(!i && lead) mul(res, dfs(pos + 1, ans, lead, limit && (i == l)));
        else mul(res, dfs(pos + 1, ans + i, 0, limit && (i == l)));
    }
    return (!limit && !lead) ? dp[pos][ans] = res : res;
}

ll part(ll x)
{
    len = 0;
    for(; x; x >>= 1) a[++len] = x & 1;
    memset(dp, -1, sizeof(dp));
    return dfs(1, 0, 1, 1);
}

int main()
{
    ll a;
    scanf("%lld", &a);
    printf("%lld
", part(a));
    return 0;
}

总结

感觉都是套路,掌握套路就好了

相信自己已经掌握了数位dp

原文地址:https://www.cnblogs.com/sugewud/p/9921560.html