题解 loj 6102 斐波那契的最小公倍数

传送门-LOJ

传送门-51Nod


【分析】

对每个质因数 (p_i) 进行 min-max 容斥得:

(displaystyle max_{p_i}(S)=sum_{varnothingsubset Tsubseteq S}(-1)^{|T|-1}min_{p_i}(T))

(displaystyle p_i^{displaystyle max_{p_i}(S)}=p_i^{displaystyle sum_{varnothingsubset Tsubseteq S}(-1)^{|T|-1}min_{p_i}(T)})

对所有 (p_i) 累乘得:

(egin{aligned} ext{lcm } fib_{{S}}&=prod_i p_i^{displaystyle max_{p_i}(T)} \&=prod_i p_i^{displaystyle sum_{varnothingsubset Tsubseteq S}(-1)^{|T|-1}min_{p_i}(T)} \&=prod_{varnothing subset Tsubseteq S} (prod_i p_i^{displaystyle min_{p_i}(T)})^{(-1)^{|T|-1}} \&=prod_{varnothing subset Tsubseteq S} (gcd fib_{{T}})^{(-1)^{|T|-1}} end{aligned})

其中,( ext{lcm }fib_{{S}}) 表示集合 (S) 内所有 (a_i) 的斐波那契值的最小公倍数,即 (displaystyle ext{lcm}_{iin S} fib_{a_i})

同理,( ext{gcd }fib_{{S}}) 表示集合 (S) 内所有 (a_i) 的斐波那契值的最大公因数,即 (displaystyle ext{gcd}_{iin S} fib_{a_i})

而由于 (gcd(fib_a, fib_b)=fib_{gcd(a, b)}) ,故逐个展开得 ( ext{gcd }fib_{{T}}=fib_{gcd {T}})

因此 (displaystyle ext{lcm } fib_{{S}}=prod_{varnothing subset Tsubseteq S} fib_{gcd {T}}^{(-1)^{|T|-1}})


直接暴力枚举 (T) 显然复杂度为 (O(2^N)) 过不了 (Nleq 5 imes 10^4) 的数据

由于 (a_i) 的值域为 ([1, 10^6]) ,考虑莫比乌斯反演,枚举 (gcd {T})

(egin{aligned} ext{lcm } fib_{{S}}&=prod_{varnothing subset Tsubseteq S} fib_{gcd {T}}^{(-1)^{|T|-1}} \&=prod_{ggeq 1}left( prod_{varnothing subset Tsubseteq Swedge gcd{T}=g} fib_g^{(-1)^{|T|-1}} ight) \&=prod_{ggeq 1} fib_g^{displaystyle sum_{varnothing subset Tsubseteq S}[gcd {T}=g](-1)^{|T|-1}} end{aligned})

(displaystyle p(g)=sum_{varnothing subset Tsubseteq S}[gcd {T}=g](-1)^{|T|-1}, q(g)=sum_{varnothing subset Tsubseteq S}[gmid gcd {T}](-1)^{|T|-1})

(displaystyle ext{lcm }fib_{{S}}=prod_{ggeq 1} fib_g^{p(g)}, q(g)=sum_{gmid m}p(m))

因此,当我们求出 (q(g)) ,再反演出 (p(g)) 后,直接对上式跑快速幂求积,即可得出答案

而对于 (q(g)) ,若我们用 (cnt_g) 表示 (g) 即其倍数的个数,则有:

(displaystyle q(g)=sum_{i=1}^{cnt_g}inom{cnt_g} i cdot (-1)^{i-1}=inom{cnt_g} 0 - [cnt_g=0]=[cnt_g>0])

故求解方法显然:

先记录每个数字的出现次数,然后用迪利克雷前缀和 (O(nlog log n)) 跑出 (cnt_g)

之后将有值的 (cnt_g)(1) ,否则置 (0) ,即 (O(n)) 得到 (q(g))

然后再用迪利克雷后缀差分 (O(nloglog n)) 跑出 (p(g))

因为 (q(g))(p(g)) 进行迪利克雷后缀和的结果

最后一边 (O(n)) 枚举斐波那契数,一边算 (fib_g^{p(g)}) 的结果即可

可将 (p(g)) 大于 (0) 与小于 (0) 的答案先分别存储,最后再对小于 (0) 的跑逆元


【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
typedef double db;
#define fi first
#define se second
const int Lim=1e6, MAXN=Lim+10, P=1000000007;
inline ll fpow(ll a,ll x) { ll ans=1; for(;x;x>>=1,a=a*a%P) if(x&1) ans=ans*a%P; return ans; }
int n, cnt[MAXN];
bool nprime[MAXN];
inline void sieve() {
    for(int i=2; i<=Lim; ++i) if(!nprime[i]) {
        for(int j=i+i; j<=Lim; j+=i)
            nprime[j]=1;
    }
}
inline void init() {
    cin>>n;
    for(int i=1, a; i<=n; ++i) {
        cin>>a;
        ++cnt[a];
    }

    for(int i=2; i<=Lim; ++i) if(!nprime[i]) {
        for(int j=Lim/i, k=j*i; j; --j, k-=i)
            cnt[j]+=cnt[k];
    }
    for(int i=1; i<=Lim; ++i) cnt[i]=(cnt[i]>0);

    for(int i=2; i<=Lim; ++i) if(!nprime[i]) {
        for(int j=1, k=i, J=Lim/i; j<=J; ++j, k+=i)
            cnt[j]-=cnt[k];
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    sieve();
    init();
    int p1=1, p2=1, fib[3]={0, 1, 1};
    for(int g=2, x; g<=Lim; ++g) {
        x = fib[g%3] = (fib[(g+1)%3]+fib[(g+2)%3])%P;
        if(cnt[g]>0) p1=(ll)p1*fpow(x, cnt[g])%P;
        else if(cnt[g]<0) p2=(ll)p2*fpow(x, -cnt[g])%P;
    }
    cout<<(ll)p1*fpow(p2, P-2)%P;
    cout.flush();
    return 0;
}
原文地址:https://www.cnblogs.com/JustinRochester/p/15220651.html