题目大意
给定一个数字序列,求该序列的所有子序列中有多少是斐波拉契数列的前缀,即满足"1 1 2 3 ..."的形式。
解题思路
本题是一道递推类的题目。
首先我们要知道子序列是指从原序列中任意选定一些项,保持其相对顺序不变,得到的序列为原序列的子序列。
也即是说,对于我当前枚举的原序列第i项,可以和原序列第1~i-1项中任意一个子序列构成一个新的子序列。
但是在本题中要求构成的序列必须满足斐波拉契数列的前缀,所以在第1~i-1项中符合要求的子序列一定满足下面这个条件:
子序列的最后一个项是原序列第i项这个数在斐波拉契数列中的前一个数
同时原序列第i
项也必须是斐波拉契中的一个数。
若枚举的第i项本身就不是斐波拉契数列中的数,则以其为结尾的子序列一定不能够成斐波拉契数列的前缀。因此我们可以先将数列当中不存在于斐波拉契数列的数先去掉,仅留下斐波拉契数列中的数。
此外我们还需要标记出原序列中每一个留下来的项对应是斐波拉契数列的第几项,方便之后的递推。需要注意的是1
在斐波拉契数列中出现了2次,因此需要特殊处理。
接下来考虑如何进行递推:
建立数组f[i][j]
: f[i][j]
表示前i
个数中,以斐波拉契数列第j
项作为结束的子序列数量。
假设我们枚举到的第i
项是斐波拉契数列中的第j
项,则有递推公式:
f[i][0] = 1 // 边界条件
f[i][j] = f[i - 1][j - 1] + f[i - 1][j]
// 1~i中以第j项结尾的子序列数量等于新产生的子序列数量f[i-1][j-1]
// 和原来已经是以第j项结尾的子序列数量f[i-1][j]之和
// 若第i项为1,则j还需要分别枚举1和2
f[i][k] = f[i - 1][k] | k ≠ j
// 其他项不会受到影响,直接继承
在题目中给定原序列中数字最大为100,000,假设斐波拉契数列中小于等于100,000的有K项。
则该递推公式的时间复杂度为O(KN),实际上也可以提前计算出K=25。
最后结果需要计算Sigma(f[N][1..K])
,表示1~N中以第1..K项结尾的子序列之和。当然在计算过程中要记得MOD 100,000,007
。
当然这题还能进一步做时间和空间的优化。
在上面的递推公式中,我们可以发现在转移过程中,f[i][j]
中大部分数都是直接继承的f[i-1][j]
。同时f[i][]
只会在枚举到第i
项的时候更新,只会在计算第i+1
项时用到。
因此我们可以省去第一维,只保留f[j]
。
这样简化之后有个需要注意的地方就是在第i项为1时,我们要先更新作为斐波拉契数列第2项的1,在计算作为斐波拉契数列第1项的1。
这是因为f[1]
更新后会影响到f[2]
,而反过来不会。
最后给出简化后的伪代码:
f[] = 0 // 初始化均为0
f[0] = 1
For i = 1 .. N
Input x
If (x in fibonacci) Then
j = order(x)
// 计算出x是斐波拉契数列中的第j项
// 若x=1,返回2,其他正常返回
f[j] = (f[j - 1] + f[j]) MOD 1,000,000,007
If (x == 1) Then // 处理作为第1项的1
f[1] = f[1] + f[0]
// 实际上f[0]永远不会改变,直接写成f[1] = f[1] + 1也可以
End If
End If
End For
Answer = 0
For j = 1 .. K
Answer = (Answer + f[j]) MOD 1,000,000,007
End For
这样空间优化到了O(K),同时时间复杂度也优化到了O(N),也就能够顺理通过所有数据了。
f[i][j] = f[i - 1][j - 1] + f[i - 1][j], 不明白该公式是如何得到的,恳请高人指点!
答主的分析的太好了,醍醐灌顶!!!