数位DP专练

数位DP专练

1

P4999 烦人的数学作业
直接数位DP,考虑前pos位,和值为sum,有无到极限(flag)
然后直接深搜

#include <iostream>
#include <cstring>
#include <cstdio>

typedef long long ll;
const ll MAXN = 1e6+10;
const ll MOD = 1e9+7;

ll L, R, lim[20], cnt, f[20][200][2], T;

ll ask(ll);
ll dfs(ll, ll, bool);

int main() {
	scanf("%lld", &T);
	while (T--) {
	    scanf("%lld%lld", &L, &R);
	    L--;
	    printf("%lld\n", (ask(R) - ask(L) + MOD) % MOD);
	}
    return 0;
}

ll ask(ll num) {
    cnt = 0;
    while (num) lim[++cnt] = num % 10, num /= 10;
    memset(f, -1, sizeof(f));
    return dfs(cnt, 0, 1) % MOD;
}

ll dfs(ll pos, ll sum, bool flag) {
    if (pos < 1) return sum % MOD;
    if (~f[pos][sum][flag]) return f[pos][sum][flag];
    ll d = flag ? lim[pos] : 9, ret = 0;
    for (ll i = 0; i <= d; i++) {
        ret = (ret + dfs(pos-1, sum + i, flag && (i == d))) % MOD;
    }
    f[pos][sum][flag] = ret % MOD;
    return ret;
}

2

P2602 ZJOI2010数字计数
每一种数字单独DP,记一下前导0,极限,和值,目前要求的数字和位置

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const ll MAXN = 1e6+10;

ll L, R, cnt, f[13][13][2][2], lim[100];

ll ask(ll, ll);
ll dfs(ll, ll, bool, bool, ll);

int main() {
    scanf("%lld%lld", &L, &R);
    L--;
    for (ll i = 0; i < 10; i++) printf("%lld ", ask(R, i) - ask(L, i));
    return 0;
}

ll ask(ll num, ll d) {
    cnt = 0;
    while (num) lim[++cnt] = num % 10, num /= 10;
    memset(f, -1, sizeof(f));
    return dfs(cnt, 0, 1, 1, d);
}

ll dfs(ll pos, ll sum, bool flag, bool zero, ll d) {
    if (pos < 1) return sum;
    if (~f[pos][sum][flag][zero]) return f[pos][sum][flag][zero];
    ll dt = flag ? lim[pos] : 9, ret = 0;
    for (ll i = 0; i <= dt; i++) {
        ret += dfs(pos - 1, sum + ((!zero || i) && (i == d)), flag && (i == lim[pos]), zero && (i == 0), d);
    }
    f[pos][sum][flag][zero] = ret;
    return ret;
}

3

P4124 CQOI2016手机号码
直接DP,记录上一位是啥,上上一位是啥,有无三连以及以上,极限,有无4,有无8。

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const ll MAXN = 1e6+10;

ll L, R, f[12][11][11][2][2][2][2], lim[MAXN], cnt = 0;

ll ask(ll);
ll dfs(ll, ll, ll, ll, ll, ll, ll);

int main() {
    scanf("%lld%lld", &L, &R);
    L--;
    printf("%lld\n", ask(R) - ask(L));
    return 0;
}

ll ask(ll num) {
	if (num < 1e10) return 0;
    memset(f, -1, sizeof(f));
    cnt = 0;
    while (num) lim[++cnt] = num % 10, num /= 10;
    ll ret = 0;
    for (ll i = 1; i <= lim[cnt]; i++) {
        ret += dfs(cnt-1, i, 0, 0, i == lim[cnt], i == 4, i == 8);
    }
    return ret;
}

ll dfs(ll pos, ll a, ll b, ll c, ll flag, ll is4, ll is8) {
    if (is4 && is8) return 0;
    if (pos < 1) return c;
    if (~f[pos][a][b][c][flag][is4][is8]) return f[pos][a][b][c][flag][is4][is8];
    ll d = flag ? lim[pos] : 9, ret = 0;
    for (ll i = 0; i <= d; i++) {
        ret += dfs(pos - 1, i, a, c || ((i == a) && (i == b)), flag && (i == d), is4 || (i == 4), is8 || (i == 8));
    }
    f[pos][a][b][c][flag][is4][is8] = ret;
    return ret;
}

4

P6218 USACO06NOV Round Numbers S
转化成2进制,直接DP,注意前导0不能统计

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const ll MAXN = 1e6+10;

ll R, L, lim[65], f[65][65][65][2][2], cnt;

ll ask(ll);
ll dfs(ll, ll, ll, bool, bool);

int main() {
    scanf("%lld%lld", &L, &R);
    L--;
    printf("%lld\n", ask(R) - ask(L));
    return 0;
}

ll ask(ll num) {
    cnt = 0;
    while (num) lim[++cnt] = num % 2, num >>= 1;
    memset(f, -1, sizeof(f));
    return dfs(cnt, 0, 0, 1, 1);
}

ll dfs(ll pos, ll numz, ll numo, bool flag, bool zero) {
    if (pos < 1) return (numz >= numo) | zero;
    if (~f[pos][numz][numo][flag][zero]) return f[pos][numz][numo][flag][zero];
    ll d = flag ? lim[pos] : 1, ret = 0;
    for (ll i = 0; i <= d; i++) {
        ret += dfs(pos-1, numz + (i == 0 && (!zero)), numo + (i == 1), flag && (i == d), zero && (i == 0));
    }
    f[pos][numz][numo][flag][zero] = ret;
    return ret;
}

5

P6754 BalticOI 2013 Day1 Palindrome-Free Numbers

直接数位DP,和萌数差不多。

也就是说,对于任意一个 \(a_i\) 只要 \(a_i \ne a_{i-1}\) 或者 \(a_i \ne a_{i-2}\) 它就不回文(很好想)

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const ll MAXN = 1e6+10;

ll L, R, X, cnt, lim[20], f[21][2][2][11][11];

ll ask(ll);
ll dfs(ll, bool, bool, ll, ll);

int main() {
    scanf("%lld%lld", &L, &R);
    L--;
    printf("%lld", ask(R) - ask(L));
    return 0;
}

ll ask(ll num) {
    cnt = 0;
    while (num) lim[++cnt] = num % 10, num /= 10;
    memset(f, -1, sizeof(f));
    return dfs(cnt, 1, 1, -1, -1);
}

ll dfs(ll pos, bool flag, bool zero, ll a, ll b) {
    if (pos < 1) return 1;
    if (~f[pos][flag][zero][a][b]) return f[pos][flag][zero][a][b];
    ll d = flag ? lim[pos] : 9, ret = 0;
    for (ll i = 0; i <= d; i++) {
        if (i == a || i == b) continue;
        ret += dfs(pos-1, flag && (i == d), zero && !i, (zero && !i) ? -1 : b, (zero && !i) ? -1 : i);
    }
    f[pos][flag][zero][a][b] = ret;
    return ret;
}

总结

数位DP通解:

int dfs(int pos /*会对决策造成影响的东西,比如极限,前导0,当前和值,连续段等等*/) {
      if (pos < 1) return /*求的值*/;
      if (/*搜过了*/) return /*搜过的值*/;
      /*一些条件的改变,例如极限*/
      for (/*能搜的数字*/) {
            /*求值*/
      }
      /*记忆化*/
      return /*返回值*/
}
posted @ 2020-09-14 09:56  Gensokyo_Alice  阅读(97)  评论(1编辑  收藏  举报