奇妙的Fibonacci(Dirichlet 前缀和)

奇妙的Fibonacci

题目描述

(听 5K 讲才知道原来是水题)
(听 qinyun 讲完发现又不是)

本文尽量解释 Fibonacci、高位前缀和 以及 Dirichlet 前缀和等,能力有限,不喜勿喷

Fibonacci

Fibonacci 数列具有一个众所周知的性质 $$ gcd(F_i,F_j)=F_{gcd(i,j)}$$

证明如下

\(\begin{aligned} F_i &=F_{i-1}+F_{i-2}\\ &=F_{i-1} \times F_{2}+F_{i-2} \times F_{1}\\ &=(F_{i-2}+F_{i-3}) \times F_{2}+F_{i-2} \times F_{1}\\ &=F_{i-2} \times (F_{1}+F_{2})+F_{i-3} \times F_{2}\\ &=F_{i-2} \times F_{3}+F_{i-3} \times F_{2}\\ &\dots\dots\\ \therefore F_{n+m} &=F_{n} \times F_{m+1} + F_{n-1} \times F_{m}\\\\ \end{aligned}\)
\(\begin{aligned} \because gcd(F_{i},F{i-1}) &=gcd(F_{i}-F_{i-1},F_{i-1})\\ &=gcd(F_{i-2},F_{i-1})\\ &\dots\dots\\ &=gcd(F_{2},F_{3})\\ &= 1\\\\ \end{aligned}\)
\(\begin{aligned} \therefore gcd(F_{n+m},F_{n}) &= gcd(F_{n} \times F_{m+1} + F_{n-1} \times F_{m},F{n})\\ &= gcd(F_{n-1} \times F_{m},F{n})\\ &= gcd(F_{n},F_{m})\\\\ \end{aligned}\)

\(\because \begin{cases} gcd(n+m,n) = gcd(n,m) \\ gcd(F_{n+m},F_{n}) = gcd(F_{n+m},F_{n}) \end{cases}\)

\(\therefore gcd(F_{n+m},F_{n})=F_{gcd(n+m,n)}\)

$\therefore F{j} \mid F{j} \Rightarrow j \mid i $

于是,我们会发现 奇妙的 Fibonacci 与 Fibonacci 一点关系都没有。其实就是求 $$i=\sum_{j \mid i}j^2$$

Dirichlet 前缀和

转化为前缀和

5K 讲的???东西,目前的理解就是求一个数的所有约数的和(或者有关约数的函数的和)。式子如下: $$b_j=\sum_{i|b_j}a_i$$

这里的 \(a_i\) 可以是一些奇奇妙妙的东西。

根据唯一分解定理:$$b=\prod_i p_i^{c_i}$$

如果我们\(b\) 的每一个质因子 \(p_i\) 看成高维空间中的一维,\(c_i\) 看成这一维方向上向量的长度

\(b\) 表示的就是空间中的一个向量(或点),而 \(b\) 的所有约数就是 \(b\) 与坐标轴围成的封闭图形中的点(以二维为例)。

那么我们求的就是 \(b\) 这个点的前缀和。

重新回忆一下前缀和,在求二维前缀和时,我们进行的操作是先横向做一遍 $$F_{i,j}=F_{i-1,j}+F{i,j}$$

然后再纵向做一遍 $$F_{i,j}=F_{i,j-1}+F_{i,j}$$

以此类推,我们求高维前缀和就是分别对每一维进行一遍上述操作。

具体如何在这道题中实现呢?

实现

以这个代码为例:

for(int i=1;i<=cnt;i++)
     for(int j=1;j*prime[i]<=n;j++)
         a[prime[i]*j]+=a[j];

a[j] 表示的就是 \(j\) 的所有约数对应的 \(a[j]\) 的和,我们把 \(j\) 分解后得到:$$j=\prod_i p_i^{c_i}$$

所以 \(j\) 也就是高维空间中的一个点(等同于上文的 \(b\)), prime[i]*j 的操作就相当于把 \(j\) 的坐标从 \((c_i ,\cdots)\) 转移到了 \((c_i +1,\cdots)\),

每一个点 \(j\) 都会更新在 \(p_i\) 维上坐标 \(+1\) 的点,在 \(p_i\) 方向上递推更新前缀和,

然后每一个质因子 \(prime\) 被遍历的过程就是遍历维度的过程,对每一维都进行类似操作后对整个空间前缀和的维护就完成了。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e7+5,mod = 1e9+7;
typedef long long LL;
LL n,q,a,b,c,ans1,ans2,s[N],t[N];
bool v[N];
int main()
{
	freopen("fibo.in","r",stdin);
	freopen("fibo.out","w",stdout);
	scanf("%lld%lld%lld%lld%lld",&n,&q,&a,&b,&c); v[1]=1;
	for(LL i=1;i<=1e7;i++) t[i]=i*i%mod,s[i]=1; 
	for(int i=2;i<=1e7;i++) if(!v[i])
		for(int j=i,k=1;j<=1e7;j+=i,k++) 
			v[j]=1, s[j]=(s[j]+s[k])%mod, t[j]=(t[j]+t[k])%mod;
	for(int i=1;i<=n;i++)
	{
		ans1=(ans1+s[q]+(q&1))%mod;
		ans2=(ans2+t[q]+(q&1)*4)%mod;
		q=(q*a+b)%c+1;
	}
	printf("%lld\n%lld\n",ans1,ans2);
	return 0;
}

复杂度 \(O(nloglogn)\),有更快的线性筛做法,但这种不用动脑子

posted @ 2024-06-14 11:51  ppllxx_9G  阅读(24)  评论(0编辑  收藏  举报