10.10 爆零赛(2023 炼石计划 NOIP 模拟赛 #2)
炼石计划 9 月 10 日 NOIP 模拟赛 #2【补题】 - 比赛 - 梦熊联盟 (mna.wang)
模拟赛恒等式:\(0+0+0+0=0\)。
复盘
T1 好像可做。有个显然的 \(n^2\) DP。推式子的时候猜到了 \(\gcd = 1\) 的做法。进一步尝试正解未果。
T2 一眼只会爆搜。想到了 \(b \times b!\) 的做法,应该能过 \(n \le 11\) 的点(实际上这也是出题人的意图)。但是测极限数据 3s,卡常未果。
T3, T4 一分不会。想的时间很少就放弃了。
剩下的时间基本都在 T2 优化(或者说卡常)。
总结
你说呢?
有个好的:T1 做完了正解的第一步。
题解
A. 数位(DP,前缀和,数论)
有 DP:\(f(i, 0/1)\) 表示考虑前 \(i\) 个数,最后一个段是否是倍数串。
转移枚举 \(j \in [0, i)\)。如果 \((j, i]\) 是倍数串那么 \(f(j,0)+f(j,1) \to f(i, 1)\),否则 \(f(j, 1) \to f(i, 0)\)。
可以轻易 \(\mathcal O(n^2)\) 预处理 \(g(j, i) = \operatorname{val}(j, i] \bmod D = (g(j,i-1) \times 10 + a_j) \bmod D\)。转移也是 \(\mathcal O(n^2)\)。考虑优化。
考虑什么时候 \(\operatorname{val}(j, i] \bmod d = 0\),即 \((j, i]\) 是一个倍数串。
类似字符串 Hash。令 \(p(i) = \operatorname{val}[i,n]\)。
首先考虑 \(\gcd(10, D) =1\) 的情况。不难发现 \((j, i]\) 是倍数串等价于 \(p(j + 1) \equiv p(i+1) \pmod D\)。读者自证不难。
然后怎么做?上面代码可以写成:
for (int i = 1; i <= n; ++ i ) {
f[i][p[1] == p[i + 1]] = 1;
for (int j = 1; j < i; ++ j ) {
if (p[j + 1] == p[i + 1]) f[i][1] = (f[i][1] + f[j][0] + f[j][1]) % P;
else f[i][0] = (f[i][0] + f[j][1]) % P;
}
}
前缀和优化即可。
考虑 \(\gcd(10, D) \ne 1\) 的情况。不妨设 \(D = 2^x5^yD'\),其中 \(10 \not \mid D’\)。因为 \(D\) 是百万级别所以 \(x, y \le 20\)。
那么 \(p(j + 1) \equiv p(i+1) \pmod D\) 当且仅当下面的条件都满足:
注意到当一个串的长度超过 \(20\) 后,前两个条件是否满足已经可以通过其低 \(20\) 位确定了。这是因为当 \(x \le 20\) 时 \(a_i \times 10^{21} \bmod 2^x = 0\),\(5\) 也同理。也就是说 \(20\) 位之后是多少,都对整个值模 \(2^x,5^y\) 后的结果没有影响。
所以当长度 \(\le 20\)(即 \(i - j \le 20\))时暴力。当长度 \(> 20\) 时,先判断其第 \(20\) 位是否满足前两个条件。注意到 \(\gcd(D', 10) = 1\)。于是又转化了最开始的问题。分类讨论一下再前缀和即可。
提交记录 #669942 - 梦熊联盟 (mna.wang)
// Problem: A. 2024--[炼石计划--NOIP模拟二]--T1 -- 数位
// Contest: LibreOJ
// URL: https://mna.wang/contest/223/problem/1
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#pragma GCC optimize(3)
#define int long long
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10, P = 1e9 + 7;
int n, a[N], d;
int f[N][2];
int val[N][22];
int N2[N][22], N5[N][22], ND[N];
int sum0[M], sum1[M];
int solve() {
string str;
cin >> str >> d;
for (int i = 0; i < d; ++ i ) sum0[i] = sum1[i] = 0;
n = str.size();
for (int i = 0; i < n; ++ i ) a[i + 1] = str[i] - '0';
int nd = d, x = 1, y = 1, l1 = 0, l2 = 0;
while (nd % 2 == 0) nd /= 2, x *= 2, l1 ++ ;
while (nd % 5 == 0) nd /= 5, y *= 5, l2 ++ ;
int lim = max(l1, l2) + 1;
f[0][1] = 1;
int sum = 0;
for (int i = 1; i <= n; ++ i )
for (int j = 1; i + j - 1 <= n && j <= lim; ++ j ) {
val[i][j] = (val[i][j - 1] * 10 + a[i + j - 1]) % d;
N2[i][j] = (N2[i][j - 1] * 10 + a[i + j - 1]) % x;
N5[i][j] = (N5[i][j - 1] * 10 + a[i + j - 1]) % y;
}
ND[n + 1] = 0;
for (int i = n, j = 1; i; -- i, j = j * 10ll % nd) {
ND[i] = (ND[i + 1] + j * a[i]) % nd;
}
for (int i = 1; i <= n; ++ i ) {
f[i][0] = f[i][1] = 0;
for (int j = max(1ll, i - lim + 1); j <= i; ++ j )
if (val[j][i - j + 1] == 0) f[i][1] = (f[i][1] + f[j - 1][0] + f[j - 1][1]) % P;
else f[i][0] = (f[i][0] + f[j - 1][1]) % P;
if (i - lim >= 0) {
f[i][0] = (f[i][0] + sum) % P;
if (N2[i - lim + 1][lim] == 0 && N5[i - lim + 1][lim] == 0) {
f[i][1] = (f[i][1] + sum0[ND[i + 1]] + sum1[ND[i + 1]]) % P;
f[i][0] = (f[i][0] - sum1[ND[i + 1]] + P) % P;
}
}
if (i >= lim) {
sum = (sum + f[i - lim][1]) % P;
sum0[ND[i - lim + 1]] = (sum0[ND[i - lim + 1]] + f[i - lim][0]) % P;
sum1[ND[i - lim + 1]] = (sum1[ND[i - lim + 1]] + f[i - lim][1]) % P;
}
}
return (f[n][0] + f[n][1]) % P;
}
signed main() {
// freopen("ex_a.in", "r", stdin);
// freopen("AC.out", "w", stdout);
freopen("digit.in", "r", stdin);
freopen("digit.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T -- ) cout << solve() << '\n';
return 0;
}

浙公网安备 33010602011771号