数位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 /*返回值*/
}
希望我们都有一个光明的未来