P11698 [ROIR 2025] 不完全质数 题解

· · 题解

传送门

声明:

本题我的代码从 kunkunge 的 0 分代码调试而来,使用已经经得他本人同意。

非常好的数位 DP 题。

最近正好学了数位 DP,直接将这一道题拿来练手了。

从题目中可以得知,质数 x 只能拆成 1 \times x,所以不完全质数的各位只能由 1,2,3,5,7 构成,且质数最终只能出现一次。

输入是经典的 l\sim r,由于此题 l 很大,无法快速算出 l-1(除非你想写高精),所以最终表达式是这样:f(r)-f(l)+\operatorname{check(l)}。其中 \operatorname{check(l)} 表示 l 是否为不完全质数,是则答案为 1,不是则答案为 0

接下来看到 $f(x)$ 如何计算。 考虑到求 $1 \sim x$ 中的个数与 $x$ 非常巨大,可以使用数位 DP 加记忆化。 而我写数位一般使用递归做法。定义递归函数 `dfs`,传 $p,lim,vis,st$ 这四个参数,分别表示现在算到了第 $p$ 位、$lim=1$ 则前面所有位置都顶着上限、$vis=1$ 则表示前面已经用过唯一的指数了,后面只能填 $1$ 了、$st=1$ 表示前面的位置都填的 $0$,即在前导零范围内。 消化了这几个参数后,就可以写题了。 在注意递归边界情况(**严格**按照我的顺序来写): 1. $vis=1$,则质数已经用过了,后面的位置只能为 $1$。如果此时顶着上线,即 $lim=1$,我们需判断后面所有的位置能否都写下 $1$(比如说 $x=120,lim=1,vis=1$ 就不行)。 2. $p=-1$,由于数位已经用完了还没有用上质数,直接返回 $0$。 3. 如果 $dp_{p,vis} \ne -1$,则说明这次已经查找过了,直接返回这个值即可。 但注意到第一个条件中每次判断时写循环判断是否能全部写下 $1$,每次到这里都要循环,时间复杂度太过巨大,可以开数组 $b$,$b_i$ 表示 $i \sim 0$ 这几位能否存下 $1$。 具体不懂的可以参照代码。 ```cpp #include <bits/stdc++.h> #define int long long int using namespace std; int dp[100005][2],b[100005]; int f[] = {0, 1, 2, 3, 5, 7}; vector<int> num; int dfs(int p, bool lim, bool vis, bool st) { if(vis) { if(p==-1) return 1; return !lim || b[p]; } if (p == -1) return 0; if (!lim && ~dp[p][vis] && !st) return dp[p][vis]; int up = lim ? num[p] : 9; int ans = 0; for (auto i : f) { if (i > up || (vis && i > 1)) break; if (st && !i) { ans += dfs(p - 1, (lim && (i == up)), 0, 1); } else { if (i == 0) continue; ans += dfs(p - 1, (lim && (i == up)), i != 1, 0); } } if (!lim && !st) dp[p][vis] = ans; return ans; } int get(string s) { reverse(s.begin(), s.end()); num.clear(); for (auto i : s) num.push_back(i - '0'); b[0]=(num[0]>=1); for(int i=1;i<num.size();i++) { if(num[i]>=2) b[i]=1; else if(num[i]==1) b[i]=b[i-1]; else b[i]=0; } return dfs(num.size() - 1, 1, 0, 1); } int check(string s) { int flag = 0; for (auto i : s) { if(i!='1') { if(i=='0' || i=='4' || i=='6' || i=='8' || i=='9') return 0; if(flag) return 0; flag=1; } } return flag; } signed main() { // freopen("1.in","r",stdin); memset(dp, -1, sizeof(dp)); string l, r; cin >> l >> r; int sum1=get(r); int sum2=get(l); cout << sum1 - sum2 + check(l); return 0; } ```