「REMAKE系列」数位dp篇

模板及技巧总结

数位DP

常前缀和思想来解决 [l,r] 范围内满足 xxx 要求的数字个数。

经典数位DP

较难写的数位DP

记搜模板

以求满足 存在 3 个连续数位单调递增的数字个数为例

ll dp[20][2][10][5]; ll dfs(int rem, int exist, int last, int inc) { // (剩余,存在满足要求的数,上一位数,当前递增个数) ll& ans = dp[rem][exist][last][inc]; if (ans != -1) return ans; if (!rem) return exist; ans = 0; for (int i = 0; i <= 9; i ++) { int new_inc = (i > last) ? min(3, inc + 1) : 1; ans += dfs(rem - 1, exist | (new_inc == 3), i, new_inc); } return ans; } ll solve(ll x) { x ++; // ++ 之后方便处理 vector<int> d; // 存每一位 while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); // 反转 int m = d.size(); ll ans = 0; // 处理前导零 for (int i = 1; i <= m - 1; i ++) { for (int j = 1; j <= 9; j++) ans += dfs(i - 1, 0, j, 1); // 第 i 位确定,还剩 i - 1 位。 } int rem = 0, exist = 0, inc = 0, last = 0; // 记录前面固定的数位的状态 for (int i = 0; i < m; i++) { for (int j = (i == 0) ? 1 : 0; j < d[i]; j++) { // 如果是最高位从 1 开始枚举 int new_inc = (j > last) ? min(3, inc + 1) : 1; // 更新下一次搜索的状态,和 dfs 里的其实很像 ans += dfs(m - i - 1, exist | (new_inc == 3), j, new_inc); } // 结束一轮枚举,更新固定数位的状态。 // 注意如果前面固定的数位已经不满足条件了,就需要 break; inc = (d[i] > last) ? min(3, inc + 1) : 1; last = d[i]; exist |= (inc == 3); } return ans; }

计数

排列计数

习题

dls动态规划中级课

数数3

dls数位DP中级课

  • 求满足 存在 3 个连续数位单调递增的数字个数
ll dp[20][2][10][5]; ll dfs(int rem, int exist, int last, int inc) { ll& ans = dp[rem][exist][last][inc]; if (ans != -1) return ans; if (!rem) return exist; ans = 0; for (int i = 0; i <= 9; i ++) { int new_inc = (i > last) ? min(3, inc + 1) : 1; ans += dfs(rem - 1, exist | (new_inc == 3), i, new_inc); } return ans; } ll solve(ll x) { x ++; vector<int> d; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(); ll ans = 0; // 处理前导零 for (int i = 1; i <= m - 1; i ++) { for (int j = 1; j <= 9; j++) ans += dfs(i - 1, 0, j, 1); } int rem = 0, exist = 0, inc = 0, last = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0) ? 1 : 0; j < d[i]; j++) { int new_inc = (j > last) ? min(3, inc + 1) : 1; ans += dfs(m - i - 1, exist | (new_inc == 3), j, new_inc); } inc = (d[i] > last) ? min(3, inc + 1) : 1; last = d[i]; exist |= (inc == 3); } return ans; }

CF1560F2. Nearest Beautiful Number (hard version)

Codeforces Round #739 (Div. 3)

  • 定义 k-美丽数 为数位中不同数位最多为 k 的数字。
  • 求大于等于 n 的最小 k-美丽数。

这类问题核心:快速判断剩下的位是否能满足要求(new_cnt <= k)

vector<int> d; int n, k; int vis[11]; // 多组数据清空 vis[],直接返回dfs不一定回溯 bool dfs(int x, bool larger, int cnt, int num) { if (x == (int)d.size()) { printf("%d\n", num); return true; } else { for (int i = larger ? 0 : d[x]; i <= 9; i ++) { vis[i]++; int new_cnt = cnt; if (vis[i] == 1) new_cnt++; if (new_cnt <= k && dfs(x + 1, larger | (i > d[x]), new_cnt, num * 10 + i)) return true; vis[i]--; } } return false; } void solve(int x) { d.clear(); while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); dfs(0, 0, 0, 0); }

不同位大于等于 k 最小数字求法

这里的核心代码就是 new_cnt + (int)d.size() - x - 1 >= k ,还要减 1 是因为当前位的贡献已经计算在了 new_cnt 里面。

vector<int> d; int n, k; int vis[11]; bool dfs(int x, bool larger, int cnt, int num) { if (x == (int)d.size()) { printf("%d\n", num); return true; } else { for (int i = larger ? 0 : d[x]; i <= 9; i ++) { vis[i]++; int new_cnt = cnt; if (vis[i] == 1) new_cnt++; if (new_cnt + (int)d.size() - x - 1 >= k && dfs(x + 1, larger | (i > d[x]), new_cnt, num * 10 + i)) return true; vis[i]--; } } return false; } void solve(int x) { d.clear(); while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); while (1) { if (dfs(0, 0, 0, 0)) break; // 当前位数搜不到就要扩大位数,否则 break int m = d.size() + 1; d.clear(); d.push_back(1); // 原来有 m 位,扩大为 m + 1 位,那么从最小的 1000000开始,有 m 个 0 for (int i = 0; i < m - 1; i ++) d.push_back(0); } }

数位背包入门——梦幻岛宝珠

P3188 [HNOI2007]梦幻岛宝珠

思路

  • 形如背包重量为 a2x 的题目,可以用数位背包的方法来尝试解题
  • 由高到低位考虑,f[i][j] 表示到第 i还剩 j2i 容量的最大价值。
  • 由于重量的系数 和 物品个数限制,考虑到第 i 位时,后面的所有位加起来贡献到第 i 位的重量是有限的,所以直接和物品重量总数取 min
  • 然后注意细节和转移,由于状态定义为还剩xxx时的最大价值,所以要用 剩的多反而背包价值更大 去更新剩的少的价值,代码中是后缀 max
const int N = 2010; ll f[N], g[N]; int n, W; // f[i][j] 到第 i 位,还剩 j 个容量的最大价值 vector<PII> item[35]; void solve() { for (int i = 0; i <= 31; i ++) item[i].clear(); int sum = 0; // 重量总数 for (int i = 0; i < n; i ++) { int w, v; re(w), re(v); int lev = __builtin_ctz(w); w >>= lev; item[lev].pb({w, v}); sum += w; } for (int i = 0; i <= sum; i++) f[i] = -2e18; f[0] = 0; for (int i = 30; i >=0 ; i--) { for (int j = 0; j <= sum; j++) g[j] = f[j], f[j] = -2e18; // 滚动数组 int d = W >> i & 1; for (int j = 0; j <= sum; j ++) { // 不选物品,i->i+1 背包状态转移,容量 * 2 + d f[min(sum, 2 * j + d)] = max(f[min(sum, 2 * j + d)], g[j]); } // 选物品,背包转移, f[i][j] = max(f[i][j], f[i - 1][j + w] + v); // w <= j <= sum, 所以需要记录 f[i][j] 为后缀 f[][] 最大值, 因为剩的更多价值更大,剩的少的价值应该被更新掉。 for (int j = sum - 1; j >= 0; j--) f[j] = max(f[j], f[j + 1]); for (auto p: item[i]) { for (int j = 0; j <= sum - p.first; j++) { f[j] = max(f[j], f[j + p.first] + p.second); } } } printf("%lld\n", f[0]); }

2020-CCPC长春-D, Meaningless Sequence

  • 有个小结论然后无脑做,有个二项式定理,(1+c)k=Σik(ci(ki))
const int N = 3010, mod = 1e9 + 7; ll power[N]; int main() { // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); string s; cin >> s; int c, n = s.size(); re(c); power[0] = 1; for (int i = 1; i <= N - 10; i++) power[i] = power[i - 1] * (c + 1) % mod; ll pre = 1, ans = 0; for (int i = 0; i < n; i ++) { if (s[i] == '0') continue; ans = (ans + pre * power[n - i - 1] % mod) % mod; pre = pre * c % mod; } ans += pre; printf("%lld\n", ans % mod); // fclose(stdin); // fclose(stdout); return 0; }

2020-ICPC济南-L. Bit Sequence

通过 (61/545)智慧暴力

思路

  • 从二进制高位向低位进行 dp,因为 m 较小,只会影响低 7 位。
  • 考虑搜索到第 8 位时,最终的结果只和 3 个东西有关。
    • 低 7 位的状态
    • 连着第 8 位的连续 1 的个数
    • 第 8 位以后的 1 的奇偶性。
  • 预处理一下低 7 位对应有效状态个数,小于等于 7 位搜索时,可以直接暴力做。
  • 放上自己写的屎山代码和网上题解代码。
#include<bits/stdc++.h> #define pb push_back #define ALL(x) x.begin(), x.end() #define popcount(x) (__builtin_popcountll(x) % 2) #define SZ(x) ((int)x.size()) using namespace std; typedef long long ll; const int N = 110; int a[N], m; ll L; int A, B, C, D, E, F, G, H; // E all yes, F all no int _popcount(int x) { int cnt = 0; for (int i = 7; i >= 0; i--) { cnt += x >> i & 1; } cnt &= 1; return cnt; } void init() { int lim = 255; A = B = C = D = E = F = G = H = 0; for (int i = 0; i <= lim; i++) { bool same = _popcount(i) == a[0]; bool ok = 1; for (int j = 0; j < m; j++) { if ((a[j] == _popcount(i + j)) != same) ok = 0; } if (ok) same ? G++ : H++; } for (int i = lim - m + 2; i <= lim; i++) { bool same = popcount(i) == a[0], changed = 0; bool ok = 1; for (int j = 1; j < m && ok; j++) { int t = _popcount(i + j); if (t == a[j] && !same) { if (!changed) { if (i + j != lim + 1) ok = 0; changed = 1, same ^= 1; } else ok = false; } else if (t != a[j] && same) { if (!changed) { if (i + j != lim + 1) ok = 0; changed = 1, same ^= 1; } else ok = false; } } if (ok && changed) { if (popcount(i) == a[0]) A ++; else B++; } } for (int i = 0; i < lim - m + 2; i++) { bool ok = 1, same = popcount(i) == a[0]; for (int j = 1; j < m && ok; j++) { if ((popcount(i + j) == a[j]) != same) ok = 0; } if (ok) same ? C ++ : D ++; } for (int i = 0; i <= 255; i++) { bool ok = 1; for (int j = 0; j < m && ok; j++) { int x = i + j; if (popcount(x) != a[j]) ok = 0; } E += ok; } for (int i = 0; i <= 255; i++) { bool ok = 1; for (int j = 0; j < m && ok; j++) { int x = i + j; if (popcount(x) == a[j]) ok = 0; } F += ok; } } ll dp[63][2][63]; ll dfs(int rem, bool cnt, int inc) { ll & ans = dp[rem][cnt][inc]; if (ans != -1) return ans; ans = 0; if (rem != 9) { ans += dfs(rem - 1, cnt, 0); ans += dfs(rem - 1, cnt ^ 1, inc + 1); } else { // fill 1 bool new_cnt = cnt ^ 1; int new_inc = inc + 1; if (new_inc & 1) { if (!new_cnt) ans += G; else ans += H; } else if (new_inc % 2 == 0) { if (!new_cnt) ans += A + C; else ans += B + D; } // fill 0 if (!cnt) ans += E; else ans += F; } return ans; } int DFS(int rem, ll val) { if (!rem) { for (int i = 0; i < m; i++) { ll x = val + i; if (popcount(x) != a[i]) return 0; } return 1; } int ans = 0; ans += DFS(rem - 1, val); ans += DFS(rem - 1, val + (1ll << (rem - 1))); return ans; } ll solve() { vector<int> d; int head = 0; for (int i = M; i >= 0; i--) { if (L >> i & 1) { head = i; break; } } for (int i = head; i >= 0; i--) { d.pb(L >> i & 1); } int len = SZ(d); ll ans = 0; ll tot = 0; for (int i = 1; i < len; i++) { if (i <= 9) ans += DFS(i - 1, 1ll << (i - 1)); else ans += dfs(i - 1, 1, 1); } bool cnt = 0; int inc = 0; ll val = 0; for (int i = 0; i < len; i++) { for (int j = (i == 0 ? 1 : 0); j < d[i]; j++) { int rem = len - i - 1; if (rem >= 9) { ans += dfs(rem, cnt, 0); } else if (rem == 8) { // fill 0 if (!cnt) ans += E; else ans += F; } else { ans += DFS(rem, val); } } cnt ^= d[i]; d[i] ? inc ++ : inc = 0; val |= ((1ll * d[i]) << (len - i - 1)); } bool ok = 1; for (int i = 0; i < m; i++) { if (popcount(L + i) != a[i]) ok = 0; } ans += ok; if (L) { ok = 1; for (int i = 0; i < m; i++) { if (popcount(i) != a[i]) ok = 0; } ans += ok; } return ans; } int main() { int T; scanf("%d", &T); while (T--) { memset(dp, -1, sizeof dp); scanf("%d%lld", &m, &L); for (int i = 0; i < m; i++) scanf("%d", &a[i]); init(); printf("%lld\n", solve()); } return 0; }

洛谷——「能力综合提升题单-数位计数DP」, Codeforces

P4999 烦人的数学作业

普及+/提高

[L,R] 数字的数位和

  • 数位dp模板题
const int mod = 1e9 + 7; int dp[20][200]; // 不能是 dp[rem][last] int dfs(int rem, int sum) { if (!rem) return sum; if (dp[rem][sum] != -1) { return dp[rem][sum]; } int& ans = dp[rem][sum]; ans = 0; for (int i = 0; i <= 9; i++) ans = (ans + dfs(rem - 1, sum + i)) % mod; return ans; } ll solve(ll x) { vector<int> d; x++; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(); ll ans = 0; for (int i = 0; i <= m - 2; i ++) { for (int j = 1; j <= 9; j++) ans = (ans + dfs(i, j)) % mod; } int sum = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0) ? 1 : 0; j < d[i]; j++) { ans = (ans + dfs(m - i - 1, sum + j)) % mod; } sum += d[i]; } return ans; } int main() { int T; re(T); memset(dp, -1, sizeof dp); while (T--) { ll l, r; re(l), re(r); printf("%lld\n", (solve(r) - solve(l - 1) + mod) % mod); } return 0; }

P2606 [ZJOI2010]排列计数

提高+/省选-二叉树小根堆、lucas定理、计数dp

int main() { int n; re(n), re(p); get_fact(); for (int i = 1; i <= n; i++) s[i] = 1; for (int i = n; i >= 2; i--) s[i >> 1] += s[i]; // C_{i - 1}^l * f[l] * f[r]; for (int i = 1; i <= n * 2 + 1; i++) dp[i] = 1; for (int i = n; i >= 1; i--) { dp[i] = lucas(s[i] - 1, s[2 * i], p) * dp[2 * i] % p * dp[2 * i + 1] % p; } printf("%lld\n", dp[1]); return 0; }

P2657 [SCOI2009] windy 数

提高+/省选-数位dp

  • 搜索状态:dp[rem][last] ,剩余多少位以及上一位是多少
int dp[12][10]; int dfs(int rem, int last) { int& ans = dp[rem][last]; if (rem == 0) return ans = 1; if (ans != -1) return ans; ans = 0; for (int i = 0; i <= 9; i++) { if (abs(i - last) < 2) continue; ans += dfs(rem - 1, i); } return ans; } int solve(int x) { x ++; vector<int> d; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(), ans = 0; for (int i = 1; i <= m - 1; i++) { for (int j = 1; j <= 9; j++) ans += dfs(i - 1, j); } int last = -2; for (int i = 0; i < m; i++) { for (int j = (i == 0) ? 1 : 0; j < d[i]; j++) { if (abs(j - last) < 2) continue; ans += dfs(m - i - 1, j); } if (abs(d[i] - last) < 2) break; // 已经不合法就不能再搜了 last = d[i]; } return ans; }

P4124 [CQOI2016]手机号码

省选/NOI-数位dp模板

出现连续 3 个相同数且不同时出现 4,8 的 11 位数。

// rem, last, exist, state inc ll dp[13][11][2][5][5]; int cal(int num, int s) { if (num == 4) s |= 1; else if (num == 8) s |= 2; return s; } ll dfs(int rem, int last, bool exist, int state, int inc) { if (!rem) return exist; ll& ans = dp[rem][last][exist][state][inc]; if (ans != -1) return ans; ans = 0; if (rem + inc < 3 && !exist) return ans; for (int i = 0; i <= 9; i++) { int new_state = cal(i, state); int new_inc = i == last ? min(3, inc + 1) : 1; int new_exist = exist | (new_inc == 3); if (new_state == 3) continue; ans += dfs(rem - 1, i, new_exist, new_state, new_inc); } return ans ; } ll solve(ll x) { x++; vector<int> d; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(); ll ans = 0; int last = -1, exist = 0, state = 0, inc = 0; for (int i = 0; i < m; i++) { if (state == 3) break; for (int j = (i == 0 ? 1 : 0); j < d[i]; j++) { int new_state = cal(j, state); if (new_state == 3) continue; int new_inc = (j == last ? min(3, inc + 1) : 1); int new_exist = exist | (new_inc == 3); ans += dfs(m - i - 1, j, new_exist, new_state, new_inc); } inc = d[i] == last ? min(inc + 1, 3) : 1; last = d[i]; state = cal(last, state); exist |= (inc == 3); } return ans; } int main() { ll l, r; re(l), re(r); memset(dp, -1, sizeof dp); printf("%lld\n", solve(r) - solve(l - 1)); return 0; }

CF628D Magic Numbers

省选+/NOI-简单数位dp

int d, M; // rem, R, ll dp[2010][2010]; string l, r; const int mod = 1e9 + 7; ll dfs(int rem, int R, bool is_even) { ll& ans = dp[rem][R]; if (ans != -1) return ans; if (!rem) return ans = R == 0; ans = 0; is_even ^= 1; for (int i = 0; i <= 9; i++) { if (is_even && i != d) continue; if (!is_even && i == d) continue; int new_R = (R * 10 + i) % M; ans += dfs(rem - 1, new_R, is_even); ans %= mod; } return ans; } bool check(string s) { int t = 0; for (int i = 0; i < SZ(s); i++) { if (i & 1 && (s[i] - '0') != d) return 0; if (i % 2 == 0 && (s[i] - '0') == d) return 0; t = (t * 10 + (s[i] - '0')) % M; } return t == 0; } ll solve(string s) { vector<int> v; for (int i = 0; i < SZ(s); i++) v.pb(s[i] - '0'); int m = v.size(); ll ans = 0; int R = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0 ? 1 : 0); j < v[i]; j++) { int new_R = (R * 10 + j) % M; if ((i % 2 == 0 && (j == d)) || (i & 1 && j != d)) continue; ans += dfs(m - i - 1, new_R, i & 1); ans %= mod; } if ((i % 2 == 0 && (v[i] == d)) || (i & 1 && v[i] != d)) break; R = (R * 10 + v[i]) % M; } if (check(s)) ans++; return ans; } int main() { IOS; string l, r; memset(dp, -1, sizeof dp); cin >> M >> d; cin >> l >> r; ll ans = (solve(r) - solve(l) + check(l) + mod) % mod; cout << ans << endl; return 0; }

P3413 SAC#1 - 萌数

省选+/NOI-数位dp、结论

回文的存在条件:存在某一位和上一位或上两位相同

const int mod = 1e9 + 7; // rem, last1, last2, exist ll dp[1010][13][13][2]; bool check(string s) { int last1 = 11, last2 = 11; for (int i = 0; i < SZ(s); i++) { int n = s[i] - '0'; if (n == last1 || n == last2) return 1; last2 = last1, last1 = n; } return 0; } ll dfs(int rem, int last1, int last2, bool exist) { ll& ans = dp[rem][last1][last2][exist]; if (ans != -1) return ans; if (!rem) return ans = exist; ans = 0; for (int i = 0; i <= 9; i++) { bool new_exist = exist | (i == last1 || i == last2); ans += dfs(rem - 1, i, last1, new_exist); ans %= mod; } return ans; } ll solve(string s) { vector<int> d; for (int i = 0; i < SZ(s); i++) d.pb(s[i] - '0'); int m = SZ(d); ll ans = 0; for (int i = 1; i < m; i++) { for (int j = 1; j <= 9; j++) { ans += dfs(i - 1, j, 11, 0); ans %= mod; } } int last1 = 11, last2 = 11; bool exist = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0 ? 1 : 0); j < d[i]; j++) { bool new_exist = exist | (j == last1 || j == last2); ans += dfs(m - i - 1, j, last1, new_exist); ans %= mod; } exist = exist | (d[i] == last1 || d[i] == last2); last2 = last1; last1 = d[i]; } ans += check(s); ans %= mod; return ans; } int main() { // IOS; string l, r; cin >> l >> r; memset(dp, -1, sizeof dp); ll ans = solve(r) - solve(l) + check(l); ans = (ans + mod) % mod; cout << ans << endl; return 0; }

P4127 [AHOI2009]同类分布

省选+/NOI-数位dp

求出 [a,b] 中各位数字之和能整除原数的数的个数。

  • 因为原数是不确定的,但原数和有范围 9×len,最多 180
  • 所以枚举所有可能的原数和(模数),复杂度是够的。
const int M = 200; // rem, R, int mod; ll dp[20][M + 2][M + 2]; vector<int> d; ll dfs(int rem, int sum, int R) { ll& ans = dp[rem][sum][R]; if (ans != -1) return ans; if (!rem) return ans = (sum == mod && (R == 0)); ans = 0; for (int i = 0; i <= 9; i++) { int new_sum = sum + i; int new_R = (R * 10 + i) % mod; if (new_sum > mod) break; ans += dfs(rem - 1, new_sum, new_R); } return ans; } ll solve2() { ll ans = 0; int m = d.size(); for (int i = 1; i < m; i++) { for (int j = 1; j <= min(9, mod); j++) { ans += dfs(i - 1, j, j % mod); } } int sum = 0, R = 0; ll val = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0 ? 1 : 0); j < d[i]; j++) { int new_R = (R * 10 + j) % mod; ans += dfs(m - i - 1, sum + j, new_R); } sum += d[i]; R = (R * 10 + d[i]) % mod; if (sum > mod) break; } return ans; } ll solve1(ll x) { d.clear(); x ++; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); ll ans = 0; for (mod = 1; mod <= SZ(d) * 9; mod ++) { memset(dp, -1, sizeof dp); ans += solve2(); } return ans; } int main() { ll l, r; cin >> l >> r; printf("%lld\n", solve1(r) - solve1(l - 1)); return 0; }

CF1073E. Segment Sum

*2300

给定 K(K10),L,R ,求 [L,R] 之间最多不包含超过 K 个数码的数的和。

思路

  • 考虑按位贡献来计算。
  • 设状态为 dp[rem][state] ,剩余 rem 位,已填数数码状态为 state 的和。
  • 由于后续还可以对应多个数,所以还需要一个数组记录剩余 rem 位,已填数码状态为 state 的数字个数,故以下转移:
    • dprem,state=jdprem1,newstate+grem1,newstate×j×10rem1
  • 实现细节:在最后一个 for 循环时,由于原位的数字并没有被计入贡献,所以需要记录每个数位后续有多少个数,因为是从高位到低位循环,而个数是从低位开始算,所以需要再循环一次。
const int mod = 998244353; int K; // rem, state; ll dp[20][1030], g[20][1030]; ll mi[22], ki[20]; void add(ll& a, ll b) { b %= mod; a += b; if (a >= mod) a -= mod; } ll dfs(int rem, int state) { ll& ans = g[rem][state]; if (ans != -1) return ans; if (!rem) { dp[rem][state] = 0; return ans = __builtin_popcount(state) <= K; } ans = 0; dp[rem][state] = 0; for (int i = 0; i <= 9; i++) { int new_state = state | (1 << i); dfs(rem - 1, new_state); add(ans, g[rem - 1][new_state]); add(dp[rem][state], dp[rem - 1][new_state] + mi[rem - 1] * i % mod * g[rem - 1][new_state] % mod); } return ans; } ll solve(ll x) { ll t = x; x ++; vector<int> d; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(); ll ans = 0; for (int i = 1; i < m; i++) { for (int j = 1; j <= 9; j++) { dfs(i - 1, 1 << j); add(ans, dp[i - 1][1 << j] + mi[i - 1] * j % mod * g[i - 1][1 << j] % mod); } } ll val = 0, state = 0; vector<int> v; ll sum = 0; for (int i = 0; i < m; i++) { ll g_cnt = 0; for (int j = (i == 0 ? 1 : 0); j < d[i]; j ++) { int new_state = state | (1 << j); dfs(m - i - 1, new_state); add(ans, dp[m - i - 1][new_state] + mi[m - i - 1] * j % mod * g[m - i - 1][new_state] % mod); add(g_cnt, g[m - i - 1][new_state]); } state |= 1 << d[i]; if (i) v.pb(g_cnt), add(sum, g_cnt); } for (int i = 0; i < SZ(v); i++) { add(ans, d[i] * mi[m - i - 1] % mod * sum % mod); sum = (sum - v[i] + mod) % mod; } return ans; } int main() { ll l, r; re(l), re(r); re(K); mi[0] = 1, ki[0] = 1; for (int i = 1; i <= 19; i++) { mi[i] = mi[i - 1] * 10 % mod; } memset(dp, -1, sizeof dp); memset(g, -1, sizeof g); printf("%lld\n", (solve(r) - solve(l - 1) + mod) % mod); return 0; }

CF55D Beautiful numbers

*2500数位dp、数论

一个数字 x 是美丽的,当且仅当 xZ+ 并且对于 x 的每一个非零位上的数 y,都有 y|x

思路

  • 找出 1-9 的最大公约数,最大是 2520
  • 套路式设计状态 dp[rem][R] 剩余 rem 位,模最大公约数的余数是 R。
  • 发现没法计算,到了最终的时候我们需要 所有数码的最大公约数 能整除 数值,这里与 R 等价,故加入状态中。
  • 直接开三维开不下,压缩最大公约数的因子个数,总共 48 个。
const int M = 2520; ll dp[20][M + 1][50]; vector<int> alls; int find(int x) { return lower_bound(ALL(alls), x) - alls.begin(); } ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ll _lcm(ll a, ll b) { return a / gcd(a, b) * b; } ll dfs(int rem, int R, int idx) { ll& ans = dp[rem][R][idx]; if (ans != -1) return ans; if (!rem) return ans = R % alls[idx] == 0; ans = 0; for (int i = 0; i <= 9; i++) { int new_R = (R * 10 + i) % M; int new_idx = idx; if (i) new_idx = find(lcm(alls[idx], i)); ans += dfs(rem - 1, new_R, new_idx); } return ans; } ll solve(ll x) { x ++; vector<int> d; while (x) { d.pb(x % 10); x /= 10; } reverse(ALL(d)); int m = d.size(); ll ans = 0; for (int i = 1; i < m; i++) { for (int j = 1; j <= 9; j++) ans += dfs(i - 1, j, find(j)); } int R = 0, idx = 0; for (int i = 0; i < m; i++) { for (int j = (i == 0 ? 1 : 0); j < d[i]; j++) { int new_R = (R * 10 + j) % M; int new_idx = idx; if (j) new_idx = find(lcm(alls[idx], j)); ans += dfs(m - i - 1, new_R, new_idx); } R = (R * 10 + d[i]) % M; if (d[i]) idx = find(lcm(alls[idx], d[i])); } return ans; } int main() { int T; re(T); memset(dp, -1, sizeof dp); for (int i = 0; i < (1 << 9); i++) { int x = 1; for (int j = 0; j < 9; j++) { if (i >> j & 1) { x = lcm(x, j + 1); } } alls.pb(x); } sort(ALL(alls)); alls.erase(unique(ALL(alls)), alls.end()); while (T--) { ll l, r; re(l), re(r); printf("%lld\n", solve(r) - solve(l - 1)); } return 0; }

CF855E. Salazar Slytherin's Locket

*2200 进制,状态压缩

评测记录

题意

l...r之间转成b进制后,0,1,2...,b2,b1都出现偶数次的数的个数。

1q105

2b10

1lr1018

思路

  • 压缩 B 进制下 B 个数的奇偶状态进行数位dp。
  • 这个题需要将进制这一维压进去,否则每次 memset 会超时。

CF401D. Roman and Numbers

*2000状态压缩+卡常数位dp

评测记录

题意

n(n1018)的各位数字重新排列(不允许有前导零),求可以构造几个 modm 等于 0 的数字。

思路

  • 读完题,一股浓烈的数位 DP 的味道。那么就开始非常套路地设计状态 dprem,R,state 表示剩余 rem 位,当前余数为 R ,剩余数的状态为 state 的合法数字个数。
    • state: 一个长度为 10 的 vector,下标 i 表示数字 i 剩余多少个数。
  • 状态转移:枚举能转移到哪些数字 j ,则有

dprem,R,state=j=09dprem1,new_R,new_state

  • 解释转移变量:new_R=(R×10+j)modmnew_statej=statej1
  • 然后就开始愉快的卡常数和复杂度,使用 map 会卡在 test 80,使用 unordered_map 并手写对 vector 的简单哈希函数可以通过本题 (3300ms~3800ms)。另外务必注意传参vector时加入引用。

区域赛真题

2020ICPC上海 C. Sum of Log

通过 99 / 682双线程数位dp

思路

  • 先发现要求的式子,就是找 i, j 最高位的累加和。
  • 然后可以进行数位DP,先把 i,j 填充位数一样方便搜索。
  • 然后智慧数位dp。
#include<bits/stdc++.h> #define pb push_back #define ALL(x) x.begin(),x.end() #define SZ(x) ((int)x.size()) typedef long long ll; using namespace std; const int mod = 1e9 + 7; int X, Y; ll dp[33][2][2][2]; vector<int> dx, dy; const int M = 32; ll dfs(int rem, bool lim1, bool lim2, bool top) { ll& ans = dp[rem][lim1][lim2][top]; if (ans != -1) return ans; ans = 0; if (!rem) return ans = 1; int up1 = lim1 ? dx[M - rem] : 1, up2 = lim2 ? dy[M - rem] : 1; for (int i = 0; i <= up1; i++) { for (int j = 0; j <= up2; j++) { if (i & j) continue; ll res = 1; if (!top && (i || j)) res = rem; ans += dfs(rem - 1, lim1 && i == up1, lim2 && j == up2, top || i || j) * res % mod; // 如果前面已经有 1 了,只需要数剩余位的个数,否则乘一个权值。 ans %= mod; } } return ans; } ll solve() { ll ans = 0; dx.clear(), dy.clear(); for (int i = M - 1; i >= 0; i--) { dx.pb(X >> i & 1), dy.pb(Y >> i & 1); } return dfs(M, 1, 1, 0); } int main() { int T; scanf("%d", &T); while (T--) { memset(dp, -1, sizeof dp); scanf("%d%d", &X, &Y); printf("%lld\n", (solve() - 1 + mod) % mod); // 计算了 0,0 } return 0; }

__EOF__

本文作者Roshin
本文链接https://www.cnblogs.com/Roshin/p/remake_digital_DP.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Roshin  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
-->
点击右上角即可分享
微信分享提示