[补档计划] 数位dp
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[补档计划] 数位dp相关的知识,希望对你有一定的参考价值。
数位dp 就是用 Dynamic Programming 解决 数位统计问题 .
[CF55D] Beautiful numbers
题意
求区间 [L, R] 中满足每个非零的数位都整除原数的数的个数.
$1\le L, R\le 9\times {10}^{18}$ .
分析
记 L 为 x 的数位的 LCM. 每个非零的数位都整数原数的数 x , 当且仅当 $x\equiv 0(\mod L)$ . 由于 $L | 2520$, 所以当且仅当 $x\mod 2520\equiv 0(\mod L)$ .
数位dp. f[pos][div][x] 表示数位不超过 pos 位, 前缀数位的 LCM 为 div , 前缀数位的和 mod 2520 为 x, 最终符合条件的数的个数.
实现
递推
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #define F(i, a, b) for (register int i = (a); i <= (b); i++) #define LL long long LL f[25][50][3000]; int id[3000], tot, rid[50]; int t; LL L, R; int bit[25], len; inline LL rd(void) { LL f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1; LL x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f; } inline LL gcd(LL a, LL b) { return !b ? a : gcd(b, a%b); } inline LL LCM(LL a, LL b) { return !a || !b ? a+b : a / gcd(a, b) * b; } inline void Transfer(int &div, int &sum, int d) { div = LCM(div, d); sum = (sum * 10 + d) % 2520; } LL Solve(LL x) { x++; while (len > 0) bit[len--] = 0; for (; x > 0; x /= 10) bit[++len] = x%10; LL res = 0; for (int d = 0; d < bit[len]; d++) res += f[len-1][id[LCM(1, d)]][d]; for (int pos = len-1, div = bit[len], sum = bit[len]; pos >= 1; pos--) { for (int d = 0; d < bit[pos]; d++) { int _div = div, _sum = sum; Transfer(_div, _sum, d); res += f[pos-1][id[_div]][_sum]; } Transfer(div, sum, bit[pos]); } return res; } int main(void) { #ifndef ONLINE_JUDGE freopen("CF55D.in", "r", stdin); freopen("CF55D.out", "w", stdout); #endif for (int i = 1; i <= 2520; i++) if (2520 % i ==0) { rid[ id[i] = ++tot ] = i; for (int j = 0; j < 2520; j += i) f[0][tot][j] = 1; } F(pos, 1, 20) F(divID, 1, tot) for (int sum = 0; sum < 2520; sum++) F(d, 0, 9) { int _div = rid[divID], _sum = sum; Transfer(_div, _sum, d); f[pos][divID][sum] += f[pos-1][id[_div]][_sum]; } t = rd(); F(c, 1, t) { L = rd(), R = rd(); LL ansR = Solve(R); printf("%I64d\n", ansR - Solve(L-1)); } return 0; }
记忆化
#include <cstdio> #include <cstring> #include <cstdlib> #define F(i, a, b) for (register int i = (a); i <= (b); i++) #define divID (id[div]) #define LL long long int id[3000], tot; int t; LL L, R; int bit[20], len; LL f[20][50][3000]; inline LL gcd(LL a, LL b) { return !b ? a : gcd(b, a % b); } inline LL LCM(LL a, LL b) { return a / gcd(a, b) * b; } LL DFS(int pos, int div, int sum, bool lim) { if (!pos) return sum % div == 0; if (!lim && ~f[pos][divID][sum]) return f[pos][divID][sum]; LL res = 0; int num = (lim ? bit[pos] : 9); F(d, 0, num) { int _div = (d == 0 ? div : LCM(div, d)); int _sum = (sum * 10 + d) % 2520; res += DFS(pos-1, _div, _sum, lim && (d == num)); } if (!lim) f[pos][divID][sum] = res; return res; } LL Solve(LL x) { while (len > 0) bit[len--] = 0; for (; x > 0; x /= 10) bit[++len] = x % 10; return DFS(len, 1, 0, true); } int main(void) { #ifndef ONLINE_JUDGE freopen("CF55D.in", "r", stdin); freopen("CF55D.out", "w", stdout); #endif for (int i = 1; i <= 2520; i++) if (2520 % i == 0) id[i] = ++tot; memset(f, -1, sizeof f); scanf("%d", &t); F(c, 1, t) { scanf("%I64d %I64d", &L, &R); LL ansR = Solve(R); printf("%I64d\n", ansR - Solve(L-1)); } return 0; }
以上是关于[补档计划] 数位dp的主要内容,如果未能解决你的问题,请参考以下文章
BZOJ 1799 [Ahoi2009] self 同类分布(数位DP)BZOJ千题计划(quexin
BZOJ 1799 [Ahoi2009] self 同类分布(数位DP)BZOJ千题计划(quexin