洛谷P4884 - 多少个1?

 OI / 洛谷
被浏览

我们通过观察可以发现 11111\cdots1 可以变化为 10n19\frac{10^{n}-1}{9},于是根据取模的性质把原式两边同乘九再加一,原式变为 10nk9+1(modm)10^{n} \equiv k \cdot 9 + 1 \pmod m

可以发现这是个 BSGS 的标准式子,由于 mm 保证为质数,所以也不必用拓展 BSGS。

然后愉快地交了一发,发现只有 80 分。再看一眼数据范围——中间过程相乘会爆 long long,所以我们需要快速乘来防止溢出。然而丧心病狂的出题人卡 lognlog_{n} 的快速乘,所以我们需要复杂度更优常数更小的快速乘:

1
2
3
4
5
6
7
// 传说中复杂度更优的O(1)快速乘
// 参考链接:https://zhuanlan.zhihu.com/p/31872064
LL mul(LL a, LL b, LL P) {
LL L = a * (b >> 25LL) % P * (1LL << 25) % P;
LL R = a * (b & ((1LL << 25) - 1)) % P;
return (L + R) % P;
}

看着这么高级其实就是利用了小学生都会的乘法分配律。

我们要计算 ab mod pa \cdot b\ mod\ p,设 b=L+Rb=L+R

那么原式就变为 a(L+R) mod p=((aL) mod p+(aR) mod p) mod pa\cdot(L+R)\ mod\ p=((a\cdot L)\ mod\ p+(a\cdot R)\ mod\ p)\ mod\ p

我们把 LL 钦定为 bb 的二进制前 xx 位,RRbb 的后 (64x)(64-x) 位。

就得到了以上的代码(以上这份代码 x=25x=25)。

用上这样的快速乘就可以 AC 了。

以下为代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <cmath>
#include <cstdio>
#include <iostream>
#include <unordered_map>
using namespace std;
typedef long long LL;
LL k, m;
// C++11标准及以上的哈希表unordered_map
unordered_map<LL, LL> Hash;
LL mul(LL a, LL b, LL P) {
LL L = a * (b >> 25LL) % P * (1LL << 25) % P;
LL R = a * (b & ((1LL << 25) - 1)) % P;
return (L + R) % P;
}
LL pw(LL a, LL n, LL P) {
LL ans = 1;
while (n) {
if (n & 1) ans = mul(ans, a, P);
a = mul(a, a, P);
n >>= 1;
}
return ans;
}
LL BSGS(LL a, LL b, LL P) {
LL len = ceil(sqrt(P));
for (LL i = 0; i < len; i++) Hash[mul(b, pw(a, i, P), P)] = i;
a = pw(a, len, P);
for (LL i = 0; i <= len; i++) {
LL x = pw(a, i, P);
LL j = Hash.count(x) ? Hash[x] : -1;
if (j >= 0 && i * len - j >= 0) return i * len - j;
}
return -1;
}
int main() {
scanf("%lld%lld", &k, &m);
k = k * 9 + 1;
k %= m;
printf("%lld", BSGS(10, k, m));
}