前言
赛时联想到了讲的一道题认为不可以使用数位 \(\rm{dp}\) , 但是那道题实际上形式上跟这个题不同, 所以其实是可以用的
思路
首先我们用数位 \(\rm{dp}\) 可以简单地解决选择数字的问题, 套路的用 \(f(1, r) - f(1, l - 1)\) 可以解决统计答案的问题, 还需要具体的讨论转移怎么做
考虑倍数的本质, 我们考虑套路的转移模 \(X\) 的结果, 但是因为 \(X \leq 10^{11}\) , 不可能直接转移
然后你发现, 我们有简单的 \(\mathcal{O} (\frac{A - B}{X})\) 算法可以枚举得到答案, 这样子我们可以简单地根号分治一下即可, 阈值什么的一会再说
同常规的数位 \(\rm{dp}\) 一样, 这个题一定也需要设计状态为 \(dp_{pos, m, lim, lead}\) 记录到达位置, 当前的模数, 是否有限制, 是否有前导 \(0\) 时的方案数
稍微特殊的是如果当前还在前导零状态下, 无论 \(S\) 的状态如何都可以继续往里面加上 \(0\) , 这也是符合条件的方案数
总时间复杂度 \(\mathcal{O} (\frac{A - B}{sz} + sz \cdot \omega)\) , 其中 \(\omega \approx 11 \times 10 = 110\) , 阈值取到 \(sz = 10^4 \sim 10^5\) 就可以了
实现
框架
数据点分治,
- \(x \leq sz\) , 使用数位 \(\rm{dp}\)
- \(x > sz\) , 使用暴力方法
数位 \(\rm{dp}\) 还是很传统的
代码
#include <bits/stdc++.h>
#define int long long
const int SZ = 10000;
const int MAXSZ = 15;
const int MAXVAL = 10000; // 记得改回来int X, A, B;
bool S[10];class BruteForces
{
private:int st;int ans = 0;bool check(int x) {while (x) { if (!S[x % 10]) return false; x /= 10; }return true;}public:void solve() {st = (A % X) ? A / X + 1 : A / X; st *= X;for (int i = st; i <= B; i += X) if (check(i)) ans++;printf("%lld", ans);}
} BF;class Intelligence
{
private:int num[MAXSZ];int dp[MAXSZ][MAXVAL][2][2];/*从高位往低位推的数位 dp 的记忆化搜索*/int dfs(int pos, int m, bool lead, bool limit) {int ans = 0;if (!pos) return (!lead && !m); // 边界条件if (~dp[pos][m][lead][limit]) return dp[pos][m][lead][limit];int up = limit ? num[pos] : 9;for (int i = 0; i <= up; i++) {if (i == 0 && lead) { ans += dfs(pos - 1, (m * 10 + i) % X, true, (i == up) & limit); continue; } // 特殊的情况if (!S[i]) continue; // 不使用违法数字ans += dfs(pos - 1, (m * 10 + i) % X, false, (i == up) & limit);}return dp[pos][m][lead][limit] = ans;}int f(int x) {int len = 0; // 递推数字串的长度while (x) { num[++len] = x % 10; x /= 10; }memset(dp, -1, sizeof(dp)); // 表示没有处理过, 而非初值(这点跟树形 dp 不同)return dfs(len, 0, true, true); // 传统}public:void solve() {printf("%lld", f(B) - f(A - 1));}
} It;signed main()
{scanf("%lld %lld %lld", &X, &A, &B);std::string a; std::cin >> a;for (int i = 0; i < a.length(); i++) S[a[i] - '0'] = true;if (X > SZ) BF.solve();else It.solve();return 0;
}
总结
根号分治思想非常好玩
枚举倍数的常见转移