1355 斐波那契的最小公倍数
原题连接:
https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1355
之前看唐老师题解学习的一个题目。顺便复习一下斐波那契数
今天想到一种相同结论。中间过程略微不一样的方法。
给定 a1,a2,a3....an
计算: LCM(Fa1,Fa2,...Fan)
这个题目其实用到了很多巧妙的结论。(需要你对斐波那契数的诸多性质有一定了解。
我们通常说的的 fib 数是指 F0=0 , F1=1 , n>0
我们在快速计算 mod 意义下的 fib 数事。通常会用下面递推矩阵:
[1110]n[FkFk−1]=[Fk+nFk+n−1]
我们看矩阵幂后里面的样子:
不需要计算。我们只需要观察。
[1110][acbd]=[a+cab+db]
从纵向看。每一项都是之前两项的的和。
那就有:
[1110]n=[Fn+1FnFnFn−1]
那么就有:
[1110]n[1110]k=[Fn+1FnFnFn−1][Fk+1FkFkFk−1]
矩阵相乘之后你会发现,不需要数学归纳法。我们就有:
Fn+k=FnFk−1+Fn+1Fk
这个式子很重要。
首先
F2n=FnFn−1+Fn+1Fn
这也就是说。 F2n 是 Fn 的倍数。
在有:
Fkn=F(k−1)n−1Fn+F(k−1)nFn+1
如果。 F(k−1)n 是 Fn 的倍数。那么意味着:
Fkn 是 Fn 的倍数。
k=2 显然成立。那么意味着 k=3,4,5,6.. 都成立
即,我们得到这样一个事实: Fkn 是 Fn 的倍数。
那么就有:
Fn , Fk,他们都是Fgcd(n,k)的倍数
所以:
gcd(Fn,Fk) 是 Fgcd(n,k)的倍数
我们有一个更确定的结论。
即:
gcd(Fn,Fk)=Fgcd(n,k)
证明上面结论我们还需要一个结论: n>1 时 , 恒有 Fn 与 Fn−1 互素。
那么(这里我们认为。 n≥k ):
gcd(Fn,Fk)=gcd(Fk,Fn mod Fk)
其中:
Fn mod Fk=(Fn−kFk−1+Fn−k+1Fk) mod Fk=Fn−kFk−1 mod Fk
继续对 Fn−k 进行上述操作。我们得到:
Fn mod Fk=Fn mod kF⌊nk⌋k−1 mod Fk
所以就有:
gcd(Fn,Fk)=gcd(Fk,Fn mod kF⌊nk⌋k−1)
因为: Fk⊥Fk−1
所以:
gcd(Fn,Fk)=gcd(Fk,Fn mod k)
进而我们有:
gcd(Fn,Fk)=Fgcd(n,k)
回到原问题:
lcm(Fn,Fk)=FnFkgcd(Fn,Fk)=FnFkFgcd(n,k)
可以肯定对是。上面对式子是不可用的。因为你会发现。他只适用于计算两个斐波那契数的最小公倍数。而不能计算一个斐波那契数和任意数字的最小公倍数。
因为斐波那契数是非常大的。答案要求对 109+7 取莫。
这就使得即使是朴素的最大公约数算法也是不可行的。
但最大公约数和最小公倍数都可以看作是质因数上的交集和并集。
有关集合之间的求解。多数与反演容斥有关。
试一试?
我们把数字看作素数的集合。
那么对于整数 a,b
则:
a⋂b=gcd(a,b)a⋃b=lcm(a,b)
a∈b
就是
a∣∣b
此时,
a
在b 中的补集合就是
ba
那么有:
answer=⋃i=1nFai
(很明显。这种表示得到的是一个多重集合。关于素因子的。而在这这种表示上。多重集合的集合元素个数的和等价于乘积。也就是说。 |a|+|b|=|ab| .一般性的容斥原理不在局限于元素个数的统计。而是集合对应的实值函数。这里。每一个素数的多重集合。都有且只对应一个整数。)
应用一般性的容斥原理:
⋃i=1nFai=∏i=1nFai∏1≤i<j≤nF−1gcd(ai,aj)∏1≤i<j<k≤nFgcd(ai,aj,ak)....
上式子没有应用价值。但可以得到很多有用的信息。
对于答案的贡献。最终体现在每一个 fib 数上。
不妨设:
answer=∏i≥1Fh[i]i
专注于 h[i] 的计算。
记。在
a[]
中取
k
个数。这k 个数的最大公约数等于
n
的方案数量为:pair(k,n)
记。在
a[]
中取
k
个数。这k 个数的最大公约数等于
n
的倍数的方案数量为:P(k,n)
则:
P(k,n)=∑n|dpair(k,d)
记。
a[]
中。
n
的倍数的个数为cnt[n]
那么
P(k,n)
等效于在这
cnt[n]
个数中取
k
个数的方案数
则:P(k,n)=(cnt[n]k)
应用经典反演有:
pair(k,n)=∑n|dμ(dn)P(k,d)
求和得到
h[n]:
h[n]=∑k≥1(−1)k+1pair(k,n)=∑k≥1(−1)k+1∑n|dμ(dn)P(k,d)=∑k≥1∑n|dμ(dn)P(k,d)(−1)k+1=∑n|d∑k≥1μ(dn)P(k,d)(−1)k+1=∑n|dμ(dn)∑k≥1(cnt[d]k)(−1)k+1
因为:
−1∗(1−1)cnt[d]=∑k≥0(cnt[d]k)(−1)k+1
∑k≥1(cnt[d]k)(−1)k+1−1=0
所以:
h[n]=∑n|dμ(dn)[cnt[d]>0]
此刻已经很欣喜了。。。。(剩下的快速幂一波出答案。)
answer=∏i≥1Fh[i]i
下面是AC代码
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define Ld(a) ((LL)a)
#define MAXN 1000005
using namespace std;
typedef long long LL;
const LL P=1e9+7;
int Ts[MAXN];
int mu[MAXN];
bool vis[MAXN];
bool G[MAXN];
int pw[MAXN];
void init(int n)
{
for(int i=3;i<n;i++)
for(int j=i;j<n;j+=i)
if(vis[j])
{
G[i]=true;
break;
}
}
void getMu(int n)
{
for(int i=1;i<=n;i++)
{
int target= i==1?1:0;
int delta= target-mu[i];
mu[i]=delta;
for(int j=i+i;j<=n;j+=i)
mu[j]+=delta;
}
}
void init2(LL n)
{
for(LL i=3;i<n;i++)
for(LL j=1;j*i<n;j++)
if(G[i*j])pw[i]+=mu[j];
}
LL extd(LL a,LL b, LL &X, LL &Y)
{
if(b==0)
{
X=1;
Y=0;
return a;
}
else
{
LL r=extd(b,a%b,Y,X);
Y-=X*(a/b);
return r;
}
}
LL pow(LL a,int b)
{
if(b<0)
{
LL y,Iva;
extd(a,P,Iva,y);
while(Iva<0)Iva+=P;
a=Iva;
b=-b;
}
LL temp=1;
while(b)
{
if(b&1) temp=(temp*a)%P;
a=(a*a)%P;
b>>=1;
}
return temp;
}
int main ()
{
int n,a,A=1;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a);
if(a<3)continue;
vis[a]=true;
A=max(A,a);
}
init(A+1);
getMu(A);
init2(A+1);
LL f1=1,f2=1,f3=2,ans=1;
for(int i=3;i<=A;i++)
{
if(pw[i])
{
ans=ans*pow(f3,pw[i]);
ans%=P;
}
f1=f3;
f3=(f3+f2)%P;
f2=f1;
}
printf("%lld\n",ans);
return 0;
}