数论题目——P1082 [NOIP2012 提高组] 同余方程、P1516 青蛙的约会、Ginger的数

P1082 [NOIP2012 提高组] 同余方程

题目描述

P1082 [NOIP2012 提高组] 同余方程 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

运行代码

#include <iostream>

using namespace std;

// 扩展欧几里得算法
void extended(long long a, long long b, long long &x, long long &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return;
    }
    extended(b, a % b, x, y);
    long long temp = x;
    x = y;
    y = temp - y * (a / b);
}

// 求解同余方程
long long solve(long long a, long long b) {
    long long x, y;
    extended(a, b, x, y);
    if (x < 0) {
        x += b;
    }
    return x;
}

int main() {
        long long a, b;
        cin >> a >> b;
        long long solution = solve(a, b);
        if (solution == -1 || (a * solution - 1) % b!= 0) {
            cout << "-1" << endl;
        } else {
            cout << solution << endl;
        }
    return 0;
}

代码思路

  1. 扩展欧几里得算法 (extended 函数):

    • 这个函数接收两个长整型参数 a 和 b,并返回满足 ax+by=gcd(a,b) 的 x 和 y 的值。这里的 gcd(a,b) 表示 a 和 b的最大公约数。
    • 函数递归地调用自身,直到 b变为0。此时,a 将会是 a 和 b 的最大公约数,且x=1,y=0,因为 a∗1+0∗0=a。
    • 在递归回退的过程中,函数更新 x 和 y 的值,直到得到原方程的解。
  2. 求解同余方程 (solve 函数):

    • 这个函数使用 extended 函数来找到 x 的值,使得ax+by=gcd(a,b) 成立。
    • 如果gcd(a,b)=1,那么方程ax≡1(modb) 有解,此时 xx 就是 aa 的逆元。
    • 如果 x<0,则加上 b 使 x落入 0 到 b-1的范围内。
  3. 主函数 (main):

    • 读取输入的 a 和 b
    • 调用 solve 函数来计算 a 在模 b 意义下的逆元。
    • 检查计算出的逆元是否正确,即验证(a∗solution−1)%b 是否等于0。如果不等于0,说明逆元计算错误或不存在,输出 -1;否则输出逆元的值。

注意事项

  • 代码中 solve 函数返回的逆元 x 应当满足 0≤x<b。
  • 当 gcd(a,b)=1 时,aa 在模 bb 意义下没有逆元,但在代码中并未显式检查这一条件,而是通过验证逆元是否正确的逻辑隐含处理了这种情况。
  • 该算法的时间复杂度接近于 O(log(min(a,b))),因为扩展欧几里得算法的时间复杂度是与输入数字的大小成对数关系的。

P1516 青蛙的约会

题目描述

P1516 青蛙的约会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

运行代码

#include <iostream>
typedef long long ll;
using namespace std;
int ex_gcd(ll a, ll b, ll& x, ll& y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    ll nx, ny;
    ll ans = ex_gcd(b, a % b, nx, ny);
    x = ny;
    y = nx - (a / b) * ny;
    return ans;
}

ll gcd(ll a, ll b) { 
    return b ? gcd(b, a % b) : a; 
}
signed main() {
    ll x, y, n, m, L;
    cin >> x >> y >> m >> n >> L;
    ll x_, y_;
    ll a = m - n;
    ll b = L;
    ll c = y - x;
    if (a < 0) {
        a = -a;
        c = -c;
    }
    ll g = ex_gcd(a, b, x_, y_);
    if (c % g) {
        cout << "Impossible\n";
        return 0;
    }
    x_ = x_ * c / g;
    cout << (x_ % (b / g) + (b / g)) % (b / g);
    return 0;
}

代码思路

  1. ex_gcd 函数:

    • 这是扩展欧几里得算法的实现,用于计算两个数的最大公约数,并同时求出满足 ax + by = gcd(a, b) 的一组整数解 x 和 y 。
    • 如果 b 为 0 ,则直接设置 x 为 1 , y 为 0 ,并返回 a 作为最大公约数。
    • 否则,先递归计算 b 和 a % b 的最大公约数和相应解,然后通过计算更新当前的 x 和 y 。
  2. gcd 函数:这是一个简单的递归函数,用于计算两个数的最大公约数,通过不断取余直到余数为 0 。

  3. main 函数:

    • 首先读入五个整数 x 、 y 、 m 、 n 和 L 。
    • 计算 a = m - n ,并根据其正负调整 a 和 c = y - x 的符号。
    • 调用 ex_gcd 函数计算 a 和 b = L 的最大公约数 g 。
    • 如果 c 不能被 g 整除,说明不存在解,输出 "Impossible" 并结束程序。
    • 否则,计算 x_ 的值,然后将其对 b / g 取模,并处理可能的负数情况,最终输出结果。

Ginger的数

题目描述

F-Ginger的数_牛客小白月赛63 (nowcoder.com)

运行代码

#include <iostream>
typedef long long ll;
using namespace std;
ll val[20];  // 用于存储一些计算中间值
// 解决问题的主要函数
void solve() {
    ll n, m;
    cin >> n >> m;  // 输入两个数 n 和 m
    m--;
    // 计算并存储每个数位的可能取值范围
    for (ll i = 1, cnt = 10; i <= 19; i++) {
        if (m > cnt) val[i] = cnt - cnt / 10;
        else val[i] = max(0LL, m + 1 - cnt / 10);
        cnt *= 10;
    }
    ll ans = 2e18;  // 初始化答案为一个极大值
    // 从高位到低位进行枚举和计算
    for (int i = 19; i >= 1; i--) {
        ll left = n;  // 剩余的数字
        ll tans = 0;  // 临时的答案累加值

        // 处理低位数字
        for (int j = i - 1; left > 0 && j >= 1; j--) {
            ll cnti = min(left / i, val[i]);  // 当前位的可能数量
            ll cntj = min((left - cnti * i) / j, val[j]);  // 低位的可能数量
            // 通过循环尝试不同的组合
            for (int t = 1; cnti >= 0 && cntj <= val[j] && t <= 30; t++) {
                if (left == cnti * i + cntj * j) {
                    ans = min(ans, tans + cnti + cntj);  // 更新最优答案
                }
                cntj++;
                if (left < cnti * i + cntj * j) {
                    cnti--;
                }
            }
            left -= val[j] * j;  // 减去低位使用的数量
            tans += val[j];  // 累加低位使用的数量
        }
    }

    // 输出结果
    if (ans > m) {
        cout << "GingerNB" << endl;
    } else {
        cout << ans << endl;
    }
}
int main() {
    int T;
    cin >> T;  // 输入测试用例数量
    while (T--) {
        solve();  // 处理每个测试用例
    }
}

代码思路

  1. solve 函数:

    • 首先读取两个整数 n 和 m,并将 m 的值减 1 进行后续处理。
    • 通过一个循环计算并填充 val 数组。对于每个位置 i,根据 m 与当前位置对应的基数 cnt 的大小关系,确定该位置可使用数字的数量。
    • 初始化最优答案 ans 为一个极大值 2e18 。
    • 外层循环从最高位(位置 19)开始向低位遍历。对于每个位置 i :
      • 初始化剩余数字 left 为 n ,临时答案累加值 tans 为 0 。
      • 内层循环处理低位 j (从 i - 1 位置开始向更低位):
        • 计算当前位置 i 可使用的数字数量 cnti 和低位 j 可使用的数字数量 cntj 。
        • 然后通过一个内层的三重循环尝试不同的 cnti 和 cntj 的组合:如果当前组合能恰好组成剩余数字 left,则更新最优答案 ans 。不断调整 cntj 和 cnti 的值,以尝试不同的组合。
        • 更新剩余数字 left 和临时答案累加值 tans 。
    • 根据最终得到的最优答案 ans 与 m 的比较,输出相应的结果。
  2. main 函数:

    • 读取测试用例的数量 T 。
    • 通过一个循环,对每个测试用例调用 solve 函数进行处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值