hihocoder


时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

Given a sequence {an}, how many non-empty sub-sequence of it is a prefix of fibonacci sequence.

A sub-sequence is a sequence that can be derived from another sequence by deleting some elements without changing the order of the remaining elements.

The fibonacci sequence is defined as below:

F1 = 1, F2 = 1

Fn = Fn-1 + Fn-2, n>=3

输入

One line with an integer n.

Second line with n integers, indicating the sequence {an}.

For 30% of the data, n<=10.

For 60% of the data, n<=1000.

For 100% of the data, n<=1000000, 0<=ai<=100000.

输出

One line with an integer, indicating the answer modulo 1,000,000,007.

样例提示

The 7 sub-sequences are:

{a2}

{a3}

{a2, a3}

{a2, a3, a4}

{a2, a3, a5}

{a2, a3, a4, a6}

{a2, a3, a5, a6}


样例输入
6
2 1 1 2 2 3
样例输出
7
根据讨论区的讨论做的
大神分析如下:

题目大意

给定一个数字序列,求该序列的所有子序列中有多少是斐波拉契数列的前缀,即满足"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),也就能够顺理通过所有数据了。

ACcode;

#include <bits/stdc++.h>
#define maxn 1000000
#define mod 1000000007
using namespace std;
int a[maxn],f[maxn],dp[maxn];
int main(){
    int n;
    f[1]=f[2]=1;
    for(int i=3;;++i){
        f[i]=f[i-1]+f[i-2];
        if(f[i]>100000)break;
    }
    while(scanf("%d",&n)!=EOF){
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            for(int j=2;j<=25;++j)
                if(a[i]==f[j]){
                    dp[j]=(dp[j-1]+dp[j])%mod;
                    if(a[i]==1)
                        dp[1]=dp[1]+1;
                    break;
                }
        }
        int ans=0;
        for(int i=1;i<=25;++i)ans=(ans+dp[i])%mod;
        cout<<ans<<'\12';
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值