[集训笔记1st](hdu-4151 Best Solver) (共轭构造/广义斐波那契数列递推/找循环节/快速幂/矩阵快速幂)

试着向教主学习看能不能写个博客记笔记。。
说不定就咕咕咕了…

题意

⌊ ( 5 + 2 6 ) 1 + 2 x ⌋ % M \lfloor\left(\\5+2\sqrt6\right)^{1+2^{x}}\rfloor\%M (5+26 )1+2x%M, x ∈ [ 0 , 2 32 ) x\in\left[\\0,2^{32}\right) x[0,232), M ≤ 46637 M \leq46637 M46637

从一个刚会矩阵快速幂的萌新视角看这道题:

难点1:无理数不能在快速幂中求模运算
难点2:指数好大啊。。
难点1的解决办法:

⌊ ( a + b ) n ⌋ % M \lfloor\left(\\a+\sqrt{b}\right)^{n}\rfloor\%M (a+b )n%M 或者 ⌈ ( a + b ) n ⌉ % M \lceil\left(\\a+\sqrt{b}\right)^{n}\rceil\%M (a+b )n%M的通用解决办法。( a − b a - b ab等于1的可以这样做)

转化为广义斐波那契数列

广义斐波那契数列的定义: F i b ( x ) = p ∗ F i b ( x − 1 ) + q ∗ F i b ( x − 2 ) Fib(x)=p * Fib(x-1) + q * Fib(x - 2) Fib(x)=pFib(x1)+qFib(x2)
广义斐波那契数列的常数矩阵:
[ p q 1 0 ] \left[\begin{matrix}p& q \\1 & 0\end{matrix} \right] [p1q0]

论证这样做的可行性:

F i b o n o c c i Fibonocci Fibonocci数列类比: F i b o n a c c i ( n ) = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] Fibonacci(n) = \frac1{\sqrt5}\left[\left(\frac{1+\sqrt5}{2}\right)^{n}-\left(\frac{1-\sqrt5}{2}\right)^{n}\right] Fibonacci(n)=5 1[(21+5 )n(215 )n]
那是不是我们要求的这个 F ( n ) F(n) F(n)也可以转化为类似的通项公式呢?
比如说:
F ( n ) = [ ( 5 + 2 6 ) n + ( 5 − 2 6 ) n ] F(n)=\left[\left(5+2\sqrt6\right)^{n}+\left(5-2\sqrt6\right)^{n}\right] F(n)=[(5+26 )n+(526 )n]
常数的改变只是因为 p p p q q q的不一样而改变了而已。
然后我们设 A = ( 5 + 2 6 ) n , B = ( 5 − 2 6 ) n A =\left(5+2\sqrt6\right)^{n}, B=\left(5-2\sqrt6\right)^{n} A=(5+26 )nB=(526 )n
考虑 A + B A+B A+B一定是个整数,因为奇数次幂的无理根式相互抵消掉了。
其次因为 5 − 2 6 5-2\sqrt6 526 是个小于1的正小数,所以 B B B也一定小于1,于是不难想到 A A A的小数部分与 B B B的小数部分一定是和为1的,那么 A A A的小数部分就是 1 − B 1-B 1B ( 5 + 2 6 ) n \left(5+2\sqrt6\right)^{n} (5+26 )n的整数部分就是 A + B − 1 A+B-1 A+B1

所以这个题的答案就转变为了求广义斐波那契数列 F n % M Fn\%M Fn%M 的值

我们说过 F n Fn Fn是一个广义斐波那契数列,那么要用矩阵快速幂计算的话就一定要求递推公式,这个不难求:
F ( 3 ) = p ∗ F ( 2 ) + q ∗ F ( 1 ) F(3) = p * F(2)+q*F(1) F(3)=pF(2)+qF(1)
F ( 2 ) = p ∗ F ( 1 ) + q ∗ F ( 0 ) F(2) = p * F(1)+q*F(0) F(2)=pF(1)+qF(0)
解二元一次方程即可得到 F ( n ) = 10 ∗ F ( n − 1 ) − F ( n − 2 ) F(n) = 10*F(n-1) - F(n-2) F(n)=10F(n1)F(n2)

我们再来解决指数过大的问题

(emmm感觉费马小定理降幂的条件不一定满足,所以我没有用。。。)

引入新的知识:

循环节

广义 F i b o n a c c i Fibonacci Fibonacci数列的第 n n n项对 M M M取模的值总是等于第 n % ( M − 1 ) ∗ ( M + 1 ) n \%\left(M-1\right)*(M+1) n%(M1)(M+1)
项对M取模的值,其中 ( M − 1 ) ∗ ( M + 1 ) (M-1)*(M+1) (M1)(M+1)就是 M M M意义下fib数列的一个循环节(不一定是最小的,是最小循环节的整数倍。)

PS:最小循环节的寻找方法

思路是打表,等到 F [ k ] = F [ 2 ] & & F [ k − 1 ] = F [ 1 ] F[k]=F[2]\&\&F[k-1]=F[1] F[k]=F[2]&&F[k1]=F[1]时, k − 2 k - 2 k2就是 M M M意义下最小的循环节。

这样是不是就解决指数过大的问题了,在对 2 x 2^{x} 2x求快速幂时,对循环节取模,然后再矩阵快速幂,就可以得到答案啦。

AC代码(递推)
#include<cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
typedef long long ll;
ll MOD, x;
ll m;
int f[500000], r[500000];

ll quick_pow(ll x, ll a) {
    ll res = x, ans = 1;
    do {
        ans = (a & 1) ? ((ans %= MOD) * (res %= MOD)) % MOD : ans;
        res *= (res %= MOD);
    } while (a >>= 1);
    return ans % MOD;
}

void init() {
    if (r[m] != -1)
        return;
    f[0] = 2 % m;
    f[1] = 10 % m;
    for (int i = 2;; ++i) {
        f[i] = ((10 * f[i - 1] - f[i - 2]) % m + m) % m;
        if (f[i - 1] == f[0] && f[i] == f[1]) {
            r[m] = i - 1;
            break;
        }
    }
}

int main() {
    int t;
    scanf("%d", &t);
    memset(r, -1, sizeof(r));
    for (int i = 1; i <= t; i++) {
        scanf("%lld%lld", &x, &m);
        init();
        MOD = r[m];
        ll k;
        k = (quick_pow(2, x) + 1) % MOD;
        f[0] = 2 % m;
        f[1] = 10 % m;
        for (int i = 2; i <= k; ++i)
            f[i] = ((10 * f[i - 1] - f[i - 2]) % m + m) % m;
        ll ans = (f[k] - 1 + m) % m;
        printf("Case #%d: %lld\n", i, ans);
    }
    return 0;
}

AC代码2(矩阵快速幂)

嫖一手大佬舍友的代码

#include<iostream>
#include<cstring>
#include<stdio.h>

using namespace std;
typedef long long ll;
const int maxn = 2;
ll n;
struct mat {
    ll mp[maxn][maxn];
};

mat mul(mat a, mat b, int mod) {
    mat temp;
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            temp.mp[i][j] = 0;
            for (int k = 0; k < 2; k++) {
                temp.mp[i][j] += (a.mp[i][k] * b.mp[k][j] % mod + mod) % mod;
            }
        }
    }
    return temp;
}

mat quick(ll b, ll mod) {
    mat a = {10, -1, 1, 0};
    mat ans = {98, 0, 10, 0};
    while (b) {
        if (b & 1)
            ans = mul(a, ans, mod);
        a = mul(a, a, mod);
        b >>= 1;
    }
    return ans;
}

ll quick2(ll a, ll b) {
    ll ans = 1;
    while (b) {
        if (b & 1)
            ans = (ans % n * a % n) % n;
        a = a * a % n;
        b >>= 1;
    }
    return ans % n;
}

int main() {
    int n0;
    cin >> n0;
    int count = 1;
    while (n0--) {
        ll a, mod, m;
        cin >> a >> mod;
        n = ((mod - 1) * (mod + 1));
        m = quick2(2, a);
        mat temp = quick(m, mod);
        ll result = (temp.mp[1][0] - 1) % mod;
        printf("Case #%d: %d", count++, result);
        cout << endl;
    }
}

这个题如果掌握了知识点的话,就是一道考验背模板能力的题。
但是因为不会知识点被学长用来提高AK难度了嘤嘤嘤。

感谢lgz学长教我的打表思路,用递推拿了一血。
感谢wjy学姐教我的循环节公式,这样就可以用矩阵快速幂做出来了。
做出来一道网络赛题目,蒟蒻有点小激动。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值