[数论][二次剩余][BSGS] CodeChef FN

2 篇文章 0 订阅
2 篇文章 0 订阅

Description

Fn 是斐波那契数列第 n 项,给定FnmodP P 求最小的n
保证 5 在膜P意义下有二次剩余。

Solution

经过一系列xjb推会发现 FnmodP 是循环的。所以答案小于 P
首先用Cipolla算法求出 5 的二次剩余。
考虑Fn的通项公式。设 x=5 y=5+2 就有

Fn=1x[yn+(y)n]
通过解一元二次方程,可以得到 yn 之后就 BSGS 求一下 n <script type="math/tex" id="MathJax-Element-20">n</script>,求的时候判下奇偶性就好啦。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

ll A, C, P, Ans, test, Yn, Y;
map<ll, ll> ap;
ll w, rtf, RT, res;
struct Complex {
    ll r, i;
    Complex(ll _r = 0, ll _i = 0):r(_r), i(_i) {}
    inline Complex operator +(ll x) {
        return Complex((x + r) % P, i);
    }
    inline Complex operator *(Complex a) {
        ll rr = (a.r * r % P + w * a.i % P * i % P + P * 3) % P,
            ii = (a.i * r % P + a.r * i % P) % P;
        return Complex(rr, ii);
    }
};

inline ll Pow(Complex a, ll b) {
    Complex c = 1;
    while (b) {
        if (b & 1) c = c * a;
        a = a * a; b >>= 1;
    }
    return c.r;
}
inline ll Pow(ll a, ll b) {
    ll c = 1;
    while (b) {
        if (b & 1) c = c * a % P;
        a = a * a % P; b >>= 1;
    }
    return (c + P) % P;
}
inline ll Inv(ll a, ll P) {
    return Pow(a, P - 2);
}
ll Log(ll a, ll b, ll p, ll par) {
    ap.clear();
    ll m = ceil(sqrt(p)), ia = Inv(Pow(a, m), P), res;
    res = Inv(b, p); par ^= 1;
    for (int i = 0; i < m; i++) {
        if (!ap.count(res)) ap[res] = i;
        res = res * a % P;
    }
    res = 1;
    for (int i = 0; i <= m; i++) {
        if (ap.count(res) && (((ap[res] + i * m) & 1) ^ par)) return ap[res] + i * m;
        res = res * ia % P;
    }
    return -1;
}
inline ll Qr(ll x, ll p) {
    if (Pow(x, (P - 1) / 2) == P - 1) return -1;
    if (Pow(x, (P - 1) / 2) == 0) return 0;
    ll a;
    Complex k(0, 1);
    while (true) {
        a = rand();
        if (Pow((a * a - x + P) % P, (P - 1) / 2) == P - 1) break;
    }
    w = (a * a - x + P) % P;
    return Pow(k + a, (P + 1) / 2);
}
inline ll Min(ll a, ll b) {
    return a < b ? a : b;
}

int main(void) {
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    scanf("%d", &test);
    srand(19260817);
    while (test--) {
        scanf("%lld%lld", &C, &P);
        rtf = Qr(5, P);
        Ans = P;
        A = C * rtf % P;
        Y = (rtf + 1) * (P + 1) / 2 % P;

        RT = Qr((A * A % P + 4) % P, P);
        if (~RT) {
            Yn = (A + RT) * (P + 1) / 2 % P;
            res = Log(Y, Yn, P, 0);
            if (~res) Ans = Min(Ans, res);
            Yn = (A - RT + P) * (P + 1) / 2 % P;
            res = Log(Y, Yn, P, 0);
            if (~res) Ans = Min(Ans, res);
        }

        RT = Qr((A * A % P - 4 + P) % P, P);
        if (~RT) {
            Yn = (A + RT) * (P + 1) / 2 % P;
            res = Log(Y, Yn, P, 1);
            if (~res) Ans = Min(Ans, res);
            Yn = (A - RT + P) * (P + 1) / 2 % P;
            res = Log(Y, Yn, P, 1);
            if (~res) Ans = Min(Ans, res);
        }
        if (Ans == P) Ans = -1;
        printf("%lld\n", Ans);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值