AcWing 最幸运的数字
8 是中国的幸运数字,如果一个数字的每一位都由 8 构成则该数字被称作是幸运数字。
现在给定一个正整数 L,请问至少多少个 8 连在一起组成的正整数(即最小幸运数字)是 L的倍数。
输入格式
输入包含多组测试用例。
每组测试用例占一行,包含一个整数 L。
当输入用例 L=0时,表示输入终止,该用例无需处理。
输出格式
每组测试用例输出结果占一行。
结果为 Case i:
+一个整数 N,N代表满足条件的最小幸运数字的位数。如果满足条件的幸运数字不存在,则 N=0。
数据范围
1≤L≤2×109
输入样例:
8
11
16
0
输出样例:
Case 1: 1
Case 2: 2
Case 3: 0
该题主要就是一个数学问题,让你找一个 x 个 8 组成的整数是 L 的倍数,并且保证找到 x 最小的可能,然后咱们首先分析一下该表达式。
L | 88..8 ,该式子表示右边是左边的倍数。然后通过分析右边的数字。
=> L | 8 * (99..9 / 9) => L | 8/9 * (10^x -1) 这两部转换还是比较简单的,一个数值的计算。
=> 9L | 8 * (10^x - 1) 左右同时乘以一个数,右边还是左边的倍数,所以可以将右边化简。
设 d = gcd(9L , 8) 因为
=> 9L/d | 8/d * (10^x-1) 两边同时除去 9L 和 8的最大公因数,那么gcd(9L/d,8/d)=1,两数互质。
=> 9L/d | 8/d * (10^x-1) 你会发现,除去最大公因数之后,两数互质那么右边是左边的倍数跟 8/d 没有关系,可以直接省略。
=> 9L/d | 10^x - 1 到这里 右边基本上到最简了
设 A = 9L/d
=> A | 10x - 1 右边是左边的倍数,则可推出下式
=> 10^x ≡ 1 (mod A) 会发现跟欧拉定理非常像
欧拉定理:对任意两个正整数 a, n,如果两者互质,那么 a^φ(n) ≡ 1( mod n)。
由定理可知,若 A 和 10 互质则有解,反之无解。
但是x == φ(A) 并不能保证这个时候的 x 是最小的解,所以根据欧拉定理,只需要枚举 φ(A)的所有因数就行了。
以上就是该题的核心思路,还涉及到一点问题。
欧拉函数的计算,因为只需要一个所以用的是 试除法 来计算的。
然后就是一个快速幂,以及龟速乘。 因为幂次方太高,数据太大不能一次计算,但是可以取模,所以用到了快速幂,龟速乘跟快速幂的思想是一样的,因为数据在1e18之内,相乘有可能爆数据,这也是快速幂的常见错误之一,这个时候就需要一个龟速乘算法来弥补,用时间来换精度。
接下来就是代码了。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) // 非常基础的最大公约数模板
{
return b?gcd(b,a%b):a;
}
ll get_euler(ll n) // 试除法 求 欧拉函数值模板
{
ll s=n;
for(ll i=2;i<=n/i;i++)
{
if(n%i==0)
{
while(n%i==0) n/=i;
s = s / i * (i - 1);
}
}
if(n>1) s=s/n*(n-1);
return s;
}
ll quick_mul(ll a,ll b,ll c) // 龟速乘模板
{
ll s=0;//注意初值为0,因为是累加。
while(b)
{
if(b&1) s = (s + a)%c;
a = (a+a)%c;
b/=2;
}
return s;
}
ll quick_mi(ll a,ll b,ll c) //快速幂模板
{
ll s=1;
while(b)
{
if(b&1) s = quick_mul(s,a,c);
a=quick_mul(a,a,c);
b/=2;
}
return s;
}
int main(void)
{
ll n;
int T = 1;
while(cin>>n,n)
{
ll mini = 1e18;
ll d=gcd(n,8),a=9*n/d,phi=get_euler(a);
// 分别对应的就是解析中的 d、A、φ(A)
if(a%2==0||a%5==0) mini=0; // 如果不是互质,根据定理直接无解。
else
for(ll i=1;i<=phi/i;i++) // 枚举约数进行判断。
if(phi%i==0) // 因为因数都是一对一对的,找到其中一个就够了,
{
if(quick_mi(10,i,a)==1) mini=min(mini,i); //判断 10^i % a 等不等于1,并且更新mini值。
if(quick_mi(10,phi/i,a)==1) mini=min(mini,phi/i);//同上
}
printf("Case %d: %lld\n", T ++ , mini);
}
return 0;
}