@bzoj - 3328@ PYXFIB


@description@

给定序列 F:\(F_0 = 1, F_1 =1, F_n = F_{n-1} + F_{n-2} (n > 1)\)

给定 n, k, p,保证 p 为质数且 p 除以 k 的余数为 1。求:
\[\sum_{i=0}^{\lfloor \frac{n}{k} \rfloor}C_{n}^{ik}\times F_{ik}\]

原题传送门。

@solution@

我们记 \(a_i = C_{n}^{i}\times F_{i}\),再记 \(G(x) = \sum a_ix^i\)。题目所求即 \(\sum a_i\times[k|i]\)

根据斐波那契通项公式,有 \(F_{i} = \frac{1}{\sqrt{5}}((\frac{1 + \sqrt{5}}{2})^{i+1} - (\frac{1 - \sqrt{5}}{2})^{i+1})\)
为了避免二次剩余问题,我们不妨将一个数记作 \(a + b\sqrt{5}\)

考虑当 k = 1 时,其实就是二项式定理。
当 k = 2 时,可以根据中学老师所教,通过将 \((a + b)^n\)\((a - b)^n\) 相加构造偶数项之和。
当 k 更大的时候,我们需要更普遍的算法。

考虑单位根 \(w_{k}^{i}\)。根据我们 fft 学到的知识,有 \(\sum_{i=0}^{k-1}w_{k}^{i\times d} = [k|d]\times d\)(这个用等比数列结合单位根的性质就可以证了)。

因此有 \(\frac{1}{k}\sum_{i=0}^{k-1}G(w_{k}^{i}) = \sum a_i\times[k|i]\)。发现 \(G(w_{k}^{i})\) 还是个二项式定理。

原根具有单位根的性质。同时题目保证 p 除以 k 的余数为 1,所以用原根代替单位根即可。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int SQ = 32000;

ll N; int K, P;

inline int add(int x, int y) {return (x + y >= P ? x + y - P : x + y);}
inline int sub(int x, int y) {return (x - y < 0 ? x - y + P : x - y);}
inline int mul(int x, int y) {return 1LL * x * y % P;}

int pow_mod(int b, int p) {
    int ret = 1;
    for(int i=p;i;i>>=1,b=mul(b,b))
        if( i & 1 ) ret = mul(ret, b);
    return ret;
}

int a[SQ + 5], acnt;
bool check_g(int x) {
    for(int i=1;i<=acnt;i++)
        if( pow_mod(x, (P - 1) / a[i]) == 1 )
            return false;
    return true;
}
int get_g() {
    int t = P - 1; acnt = 0;
    for(int i=2;i<=SQ;i++) {
        if( t % i == 0 ) {
            a[++acnt] = i;
            while( t % i == 0 )
                t /= i;
        }
    }
    if( t != 1 ) a[++acnt] = t;
    
    for(int i=2;;i++)
        if( check_g(i) ) return i;
}

struct mint{
    int a, b; mint() {}
    mint(int _x) : a(_x), b(0) {}
    mint(int _a, int _b) : a(_a), b(_b) {}
    friend mint operator + (mint a, mint b) {
        return mint(add(a.a, b.a), add(a.b, b.b));
    }
    friend mint operator - (mint a, mint b) {
        return mint(sub(a.a, b.a), sub(a.b, b.b));
    }
    friend mint operator * (mint a, mint b) {
        int p = add(mul(a.a, b.a), mul(5, mul(a.b, b.b)));
        int q = add(mul(a.a, b.b), mul(a.b, b.a));
        return mint(p, q);
    }
    friend mint operator / (mint a, int b) {
        int k = pow_mod(b, P - 2);
        return a * k;
    }
};

mint pow(mint b,ll p) {
    mint ret = 1;
    for(ll i=p;i;i>>=1,b=b*b)
        if( i & 1 ) ret = ret*b;
    return ret;
}

int cal(int x) {
    mint A = mint(1, 1) / 2;
    int ret = (pow(A * x + 1, N) * A).b;
    A = mint(1, P - 1) / 2;
    ret = sub(ret, (pow(A * x + 1, N) * A).b);
    return ret;
}
void solve() {
    scanf("%lld%d%d", &N, &K, &P);
    int g = get_g(), w = pow_mod(g, (P - 1)/K), ans = 0;
    for(int i=0,p=1;i<K;i++,p=mul(p, w))
        ans = add(ans, cal(p));
    printf("%d\n", mul(ans, pow_mod(K, P - 2)));
}

int main() {
    int T; scanf("%d", &T);
    while( T-- ) solve();
}

@details@

这种地方能用通项公式的话没有必要用矩阵求幂了,想起来也比较复杂。

猜你喜欢

转载自www.cnblogs.com/Tiw-Air-OAO/p/12419278.html