Files
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

314 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##[$AcWing$ $202$. 最幸运的数字](https://www.acwing.com/problem/content/description/204/)
### 一、题目描述
$8$ 是中国的幸运数字,如果一个数字的每一位都由$8$构成则该数字被称作是幸运数字。
现在给定一个正整数 $L$,请问 **至少多少个$8$连在一起组成的正整数**(即最小幸运数字)是$L$的倍数。
**输入格式**
输入包含多组测试用例。
每组测试用例占一行,包含一个整数 $L$。
当输入用例 $L=0$ 时,表示输入终止,该用例无需处理。
**输出格式**
每组测试用例输出结果占一行。
结果为 `Case i:`+一个整数 $N$,$N$ 代表满足条件的最小幸运数字的位数。
如果满足条件的幸运数字不存在,则 $N=0$。
**数据范围**
$1≤L≤2×10^9$
**输入样例**
```cpp {.line-numbers}
8
11
16
0
```
**输出样例**
```cpp {.line-numbers}
Case 1: 1
Case 2: 2
Case 3: 0
```
### 二、前导知识
用到了 $6$个前导知识点:
**1. 由 $x$ 个 $y$ 组成的数的公式是$\large \displaystyle y \times \frac{10^x-1}{9}$,比如 $88888888$这种数。**
$\large \displaystyle \underbrace{8...8}_{x}=8 \times \underbrace{1...1}_{x}=8 \times \frac{\underbrace{9...9}_{x}}{9}=8 \times \frac{10^x-1}{9}$
**2. 欧拉定理**
若正整数$a, n$互质,则 $$\large a^{φ(n)}≡1(mod \ n)$$
其中$\phi(n)$指欧拉函数值,$(1\sim n)$与$n$互质的数。
**3. 欧拉定理推论**
若正整数$a,n$互质,则满足$a^x \equiv 1 \ (mod \ n)$的 **最小正整数解** $x_0$是$\phi(n)$的约数。
**反证法**:假设该最小正整数 $x$不是 $φ(c)$ 的约数
则 $φ(c)=qx+r(0<r<x)$,
又
$$
\large \left\{\begin{matrix}
& 10^x \equiv 1 \ (mod\ c) \Rightarrow 10^{qx} \equiv 1 (mod \ c) \ ①\\
& 10^{φ(c)} \equiv 1 \ (mod\ c) \Rightarrow 10^{qx+r} \equiv 1 (mod \ c) \ ②
\end{matrix}\right.
$$
由 ② 式除以 ① 式得 $10^r≡1 \ (mod \ c)(0<r<x)$
故存在比 $x$ 更小的正整数满足该式子,矛盾了
因此得证
**4. 快速幂**
```cpp {.line-numbers}
int qmi(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
```
**5. 快速幂+快速乘**
当模数$>1e9$的时候,在相乘的时候爆$long$ $long$了。
```cpp {.line-numbers}
//快速乘
//时间复杂度:logn,乘法本来是O(1),这么干成了O(logN)了。
//优点:可以防止在LL*LL % MOD 过程中爆掉LL,可以将乘法分解成多步加法,在加的过程中不断取模。
int qmul(int a, int b, int mod) {
LL res = 0;
while (b) {
if (b & 1) res = (res + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return res;
}
//快速幂+快速乘
int qmi(int a, int b, int mod) {
int res = 1;
while (b) {
if (b & 1) res = qmul(res, a, mod); //调用龟速乘,原因是本题的数据范围是2*1e9,直接乘会爆掉LL,需要一路乘来一路取模
a = ksc(a, a, mod);
b >>= 1;
}
return res;
}
```
**6. $\_\_int128$配合快速幂**
```cpp {.line-numbers}
typedef __int128 LLL;
//快速幂+ LLL
int ksm(int a, int b, int mod) {
int res = 1;
while (b) {
if (b & 1) res = (LLL)res * a % mod;
a = (LLL)a * a % mod;
b >>= 1;
}
return res;
}
```
### 三、本题思路
题目实际上要我们求一个最小的正整数 $x$,满足 $\large \displaystyle L|8*\frac{10^x-1}{9}$
转化条件:
$$\large \displaystyle \Leftrightarrow 9*L | 8 * (10^x-1)$$
令$\large \displaystyle d=gcd(L,8)$,约分:
$$\large \displaystyle \Leftrightarrow \frac{9*L}{d} | 10^x-1 \ \ \ \ ①$$
$$\large \displaystyle \Leftrightarrow 10^x-1 \equiv 0 \ (mod \ \frac{9*L}{d})$$
$$\large \displaystyle \Leftrightarrow 10^x \equiv 1 \ (mod \ \frac{9*L}{d})$$
设$\large \displaystyle p=\frac{9*L}{d}$
即:
$$\large \displaystyle \Leftrightarrow 10^x \equiv 1 \ (mod \ p)$$
最终变成 <font color='red' size=4><b>求解满足 $\large \displaystyle 10^x≡1(mod \ p)$ 的最小正整数 $x$</b></font>
> **注释**
> $d$既然是两个数的最大公约数,那么两个数都除以$d$后,得到的两个新数,必然互质 (最大公约数的定义)
> 即:$\large \displaystyle \frac{9*L}{d}$与$\large \displaystyle \frac{8}{d}$是互质的,而$\large \displaystyle 10^x-1$还想是$\large \displaystyle \frac{9*L}{d}$的整数倍
> 所以 $\large \displaystyle \frac{9*L}{d}$一定是$\large \displaystyle 10^x-1$的因数,所以$\large \displaystyle : \frac{9*L}{d} | 10^x-1$
### 四、欧拉定理+推论
欧拉定理: $\large \displaystyle a^{φ(n)}≡1(mod \ n)$(其中$\large \displaystyle gcd(a,n)=1$)
这里的形式有点像欧拉定理,$\large a^{\phi(p)}\equiv 1 (mod \ p)$但是欧拉定理只是说$\large φ(p)$是一个解,但不能保证 $\large φ(p)$ 是满足等式的最小正整数!
由于我们学习过前导知识,知道如果想求最小正整数解$\large x_0$,其实需要满足$\large x_0|\phi(n)$,我们只需要枚举每个$\large \phi(n)$的约数,找出满足条件的最小正整数$\large x_0$即可。
### 五、实现代码(快速幂+快速乘)
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
// 最大公约数
int gcd(int a, int b) {
if (b == 0) return a;
return b ? a : gcd(b, a % b);
}
// 快速乘
int ksc(int a, int b, int mod) {
int res = 0;
while (b) {
if (b & 1) res = (res + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return res;
}
// 快速幂+快速乘
int ksm(int a, int b, int mod) {
int res = 1;
while (b) {
if (b & 1) res = ksc(res, a, mod);
a = ksc(a, a, mod);
b >>= 1;
}
return res;
}
/**
* 功能:计算单个数字的欧拉函数值
* @param x
* @return
*/
int phi(int x) {
int res = x;
for (int i = 2; i <= x / i; i++)
if (x % i == 0) {
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
signed main() {
int T = 1; // 准备输出Case i
int L;
while (cin >> L, L) {
int d = gcd(L, 8); // L和8的最大公约数 gcd(8,L)
int mod = 9 * L / d; // 公式推导得到的mod
int p = phi(mod); // 单个数字mod的欧拉函数值φ(mod)
int res = LONG_LONG_MAX; // 预求最小先设最大
if (gcd(mod, 10) > 1) // 判断mod和10是否互质,不互质:方程无解,输出0
res = 0;
else {
for (int d = 1; d * d <= p; d++) // 枚举φ(p)的每个小约数,到sqrt(φ(p))
if (p % d == 0) { // 如果d是约数,那么其实我们一次发现了两个约数: d 和 phi/d
// 这里与原来的质因子分解有一点点不同,因为那个只要枚举小于sqrt(n)的,并且 n %d==0,
// 就一定是它的小质数因子,本题不是这个意思。
// 本题的目标是找出所有因子,注意,不是小因子。
// 因为,小因子不见得能是方程的解,大因子不见得不是方程的解!!
// 对于它们,都有机会成为答案,需要全部讨论到!然后PK最小值即可!
// 如果这个约数d,满足 10 ^ d ≡ 1 (mod p) ,那么它有机会成为答案
if (ksm(10, d, mod) == 1) res = min(res, d); // 小约数
// 如果这个约数phi/d,满足 10 ^ (phi/d) ≡ 1 (mod p) ,那么它有机会成为答案
if (ksm(10, p / d, mod) == 1) res = min(res, p / d); // 大约数
}
}
// 输出
printf("Case %d: %lld\n", T++, res);
}
}
```
### 六、快速幂+$\_\_int128$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
typedef unsigned __int128 INT128;
// 最大公约数
int gcd(int a, int b) {
if (b == 0) return a;
return b ? a : gcd(b, a % b);
}
// 快速幂+ INT128
int qmi(int a, int b, int mod) {
int res = 1;
while (b) {
if (b & 1) res = (INT128)res * a % mod;
a = (INT128)a * a % mod;
b >>= 1;
}
return res;
}
/**
* 功能:求单个数字的欧拉函数值
* @param x
* @return
*/
int phi(int x) {
int res = x;
for (int i = 2; i <= x / i; i++)
if (x % i == 0) {
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
signed main() {
int T = 1; // 准备输出Case i
int L;
while (cin >> L, L) {
int d = gcd(L, 8); // L和8的最大公约数 gcd(8,L)
int mod = 9 * L / d; // 公式推导得到的mod
int p = phi(mod); // 单个数字mod的欧拉函数值φ(mod)
int res = LONG_LONG_MAX; // 预求最小先设最大
if (gcd(mod, 10) > 1) // 判断p和10是否互质,不互质:方程无解,输出0
res = 0;
else {
for (int d = 1; d * d <= p; d++) // 枚举φ(p)的每个小约数,到sqrt(φ(p))
if (p % d == 0) { // 如果d是约数,那么其实我们一次发现了两个约数: d 和 phi/d
// 这里与原来的质因子分解有一点点不同,因为那个只要枚举小于sqrt(n)的,并且 n %d==0,
// 就一定是它的小质数因子,本题不是这个意思。
// 本题的目标是找出所有因子,注意,不是小因子。
// 因为,小因子不见得能是方程的解,大因子不见得不是方程的解!!
// 对于它们,都有机会成为答案,需要全部讨论到!然后PK最小值即可!
// 如果这个约数d,满足 10 ^ d ≡ 1 (mod p) ,那么它有机会成为答案
if (qmi(10, d, mod) == 1) res = min(res, d); // 小约数
// 如果这个约数phi/d,满足 10 ^ (phi/d) ≡ 1 (mod p) ,那么它有机会成为答案
if (qmi(10, p / d, mod) == 1) res = min(res, p / d); // 大约数
}
}
// 输出
printf("Case %d: %lld\n", T++, res);
}
}
```