题解:P15132 [ROIR 2026] 位魔法

· · 题解

看到按位与,首先需要想到二进制。再看到 16^{5 \times 10^4} 的数据范围,很容易想到数位 DP。

十六进制转二进制是好做的,每个十六进制数位都代表 4 个二进制数位,例如 B_{(16)} 代表 $1011_{(2)}

为了方便下面的 DP,需要注意把 $l, r, b$ 这三个数对齐,详见代码。 现在考虑对二进制串数位 DP,转化成差分:$[0, r]$ 内的合法方案数减去 $[0, l]$ 内的合法方案数加上 $l$ 是否合法。因为让 $l$ 减一有点麻烦,所以转化成如上差分。下面考虑 $[0, t]$ 内的合法方案数。 这个就比较模板了,我个人习惯记忆化搜索。设 $dp_{pos, lm}$ 表示到了第 $pos$ 位,先前是否顶到上界,此时的方案数。对于当前的第 $pos$ 位,设填充的数字从 $st$ 枚举到 $ed$,若 $b$ 串第 $pos$ 位为 $0$,则 $st = 0$ 表示随意;否则 $st = 1$ 表示必须填 $1$。若 $lm = 1$ 则 $ed = t_{pos}$,否则 $ed = 1$。然后继续递归即可。由于全 $0$ 也可能合法,因此不需要记录前导零。 时间复杂度 $O(n)$,$n$ 为二进制串长度。 ```cpp int cti[150]; const int N = 200005; char hl[N], hr[N], hb[N]; int n, l[N], r[N], b[N]; inline void init(char *hc, int *c){ int m = strlen(hc + 1); int top = (n - m) << 2; // 对齐。 for(int i = 1; i <= m; i++){ int x = cti[(int)hc[i]]; c[++top] = x >> 3; c[++top] = x >> 2 & 1; c[++top] = x >> 1 & 1; c[++top] = x & 1; } } const int mod = 1000000007; inline void ADD(int &x, int y){ (x += y) >= mod && (x -= mod); } inline int check(){ for(int i = 1; i <= n; i++) if(b[i] && !l[i]) return 0; return 1; } int maxn[N], dp[N][2]; int dfs(int pos, bool limit){ if(pos == n + 1) return 1; if(dp[pos][limit]) return dp[pos][limit]; int res = 0, st = (b[pos] ? 1 : 0), lim = (limit ? maxn[pos] : 1); for(int i = st; i <= lim; i++) ADD(res, dfs(pos + 1, limit && (i == lim))); return dp[pos][limit] = res; } inline int solve(int *ep){ memset(dp, 0, sizeof(dp)); for(int i = 1; i <= n; i++) maxn[i] = ep[i]; return dfs(1, true); } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); for(int i = '0'; i <= '9'; i++) cti[i] = i - '0'; for(int i = 'A'; i <= 'F'; i++) cti[i] = i - 'A' + 10; cin >> (hl + 1) >> (hr + 1) >> (hb + 1); n = max({strlen(hl + 1), strlen(hr + 1), strlen(hb + 1)}); init(hl, l), init(hr, r), init(hb, b), n <<= 2; cout << (solve(r) - solve(l) + check() + mod) % mod << '\n'; return 0; } // the name of the music at submission time: 《》 ```