poj 3696 + poj 3358 (欧拉函数进阶)

本文深入探讨了解决两个数学算法难题的方法:POJ3696和POJ3358。POJ3696关注于寻找能整除特定数且所有位都是8的最小数位数,通过引入数学公式和欧拉函数,简化问题求解过程。POJ3358则涉及到在二进制下寻找最小循环前缀和最小循环起始位,利用欧拉定理和同余方程,揭示了循环节的起始位置。本文旨在提供清晰的解析路径和代码实现,帮助读者理解和解决此类复杂数学问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

poj 3696:

题意:

输入一个数L(1 <= L <= 2,000,000,000)。。。

求能整除L且所有位上的数字都是8的数的最小位是几位。


解析:

高中数学课上安哥讲这个的时候真心觉得屌,一个有x位且每位数字都有相同的数字u的数,可以这样表示:

         (10 ^ x - 1) / 9 × u 。

这题,设长度为x的由8组成的数可以被L整除,则:

       (10 ^ x - 1 ) / 9  * 8 = L * p .

即: 8 * (10 ^ x - 1 ) = L * p * 9 .

设: d = gcd( 8, L ) .

则: 8 / d * (10 ^ x - 1 ) = L / d * 9 * p  .

设: 8 / d 为 a, L / d * 9为b . 易知 a, b 互素 .

则: a * (10 ^ x - 1 ) = b * p .

由于 a, b 互素: 10 ^ x  = 1 (mod b)

ps. 设 a * c = b * p, a,b互素,可知 a * c = 0 (mod b),又由于a,b互素,所以:c = 0 (mod b).

所以方程转换为求同余方程10 ^ x  = 1 (mod b)的最小x解。

若gcd(10, b) != 1,则原方程无解;

若有解,根据欧拉公式可以知道 10 ^ phi(b) = 1 mod (b),枚举phi(b)的因子,找到最小的满足欧拉公式的数即可。


注意点是快速幂里面还要再套快速乘,因为超LL,只能转成加法做。


代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long

using namespace std;
const int maxn = (1 << 16);
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = 4 * atan(1.0);
const double ee = exp(1.0);

LL euler_phi(LL n)
{
    LL m = sqrt(n + 0.5);
    LL res = n;
    for (LL i = 2; i <= m; i++)
    {
        if (n % i == 0)
        {
            res = res / i * (i - 1);
            while (n % i == 0)
                n /= i;
        }
    }
    if (1 < n)
        res = res / n * (n - 1);
    return res;
}

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

LL mult_mod(LL a, LL b, LL mod)
{
    LL res = 0;
    while (b)
    {
        if (b & 1)
            res = (res + a) % mod;
        a = 2 * a % mod;
        b >>= 1;
    }
    return res;
}

LL pow_mod(LL a, LL n, LL mod)
{
    if (n == 0)
        return 1;
    LL x = pow_mod(a, n >> 1, mod);
    LL res = mult_mod(x, x, mod);
    if (n % 2)
        res = mult_mod(res, a, mod);
    return res;
}

int cnt;
LL factor[maxn];
void Divid(LL t)
{
    cnt = 0;
    memset(factor, 0, sizeof(factor));
    for (LL i = 1; i * i <= t; i++)
    {
        if (t % i == 0)
        {
            factor[cnt++] = i;
            factor[cnt++] = t / i;
        }
    }
    sort(factor, factor + cnt);
}

LL solve(LL m)
{
    LL t = euler_phi(m);
    Divid(t);
    for (int i = 0; i < cnt; i++)
        if (pow_mod(10, factor[i], m) == 1)
            return factor[i];
    return 0;
}

int main()
{
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
#endif // LOCAL
    int ca = 1;
    LL m;
    while (scanf("%lld", &m) && m)
    {
        printf("Case %d: ", ca++);
        m = m / gcd(m, 8) * 9;
        if (gcd(10, m) != 1)
        {
            printf("0\n");
        }
        else
        {
            printf("%lld\n", solve(m));
        }
    }
    return 0;
}

poj 3358:

题意:

给pq,代表一个小数p/q,求在二进制下这个小数的最小循环前缀和最小循环起始位。


解析:

小数二进制的转换方法就是不断*2,mod 分母。

比如:1/10  2/10  4/10  8/10  16/10  32/10...

模后:1/10  2/10  4/10  8/10    6/10    2/10...

这时出现了重复,并且这个重复就是最小循环。

设从x位置到y位置循环节的分子表示为:2^y = 2^x (mod p)

稍微变身一下:2^y - 2^x = 0 (mod p) ,

                            2^x(2^(y - x ) - 1) = 0 (mod p),

所以: p | 2^x(2^(y - x ) - 1),把p一直除到不能被整除就是最小的循环部分起始位置x,

然后上式变成了:2^(y-x)   -  1 = 0 (mod p').

即: 2^(y - x)  =  1 (mod p').

又到达了欧拉定理的某种设定之中,根据欧拉定理,t = y - x = elur_phi(p'),枚举同上从小到大枚举 t  的约数,满足此式子就得解。


代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long

using namespace std;
const int maxn = (1 << 16);
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = 4 * atan(1.0);
const double ee = exp(1.0);

LL euler_phi(LL n)
{
    LL m = sqrt(n + 0.5);
    LL res = n;
    for (LL i = 2; i <= m; i++)
    {
        if (n % i == 0)
        {
            res = res / i * (i - 1);
            while (n % i == 0)
                n /= i;
        }
    }
    if (1 < n)
        res = res / n * (n - 1);
    return res;
}

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

LL mult_mod(LL a, LL b, LL mod)
{
    LL res = 0;
    while (b)
    {
        if (b & 1)
            res = (res + a) % mod;
        a = 2 * a % mod;
        b >>= 1;
    }
    return res;
}

LL pow_mod(LL a, LL n, LL mod)
{
    if (n == 0)
        return 1;
    LL x = pow_mod(a, n >> 1, mod);
    LL res = mult_mod(x, x, mod);
    if (n % 2)
        res = mult_mod(res, a, mod);
    return res;
}

int cnt;
LL factor[maxn];
void Divid(LL t)
{
    cnt = 0;
    memset(factor, 0, sizeof(factor));
    for (LL i = 1; i * i <= t; i++)
    {
        if (t % i == 0)
        {
            factor[cnt++] = i;
            factor[cnt++] = t / i;
        }
    }
    sort(factor, factor + cnt);
}

int main()
{
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
#endif // LOCAL
    int ca = 1;
    LL m, n;
    LL ans1, ans2;
    while (scanf("%lld/%lld", &n, &m) != EOF)
    {
        LL d = gcd(n, m);
        n /= d;
        m /= d;
        LL t = 1;
        while (m % 2 == 0)
        {
            t++;
            m /= 2;
        }
        ans1 = t;
        LL phi = euler_phi(m);
        if (phi == 1)
        {
            ans2 = 1;
        }
        else
        {
            Divid(phi);
            for (int i = 0; i < cnt; i++)
            {
                if (pow_mod(2, factor[i], m) == 1)
                {
                    ans2 = factor[i];
                    break;
                }
            }

        }
        printf("Case #%d: %lld,%lld\n", ca++, ans1, ans2);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值