@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@
这种地方能用通项公式的话没有必要用矩阵求幂了,想起来也比较复杂。