[旧题新解]HDU1005 Number Sequence

本文探讨了一种特定的递归数列求值方法,包括寻找循环规律的直接方法及利用矩阵快速幂优化计算的高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

f(1)=f(2)=1f(1)=f(2)=1

f(n)=(Af(n1)+Bf(n2))mod7f(n)=(A∗f(n−1)+B∗f(n−2))mod7

1A,B1,000,1n1081≤A,B≤1,000,1≤n≤108

f(n)f(n)

思路

看到此题,一种比较平凡的思路就是找规律。很多人的直觉是,循环节的长度必然是49。想到这种想法,主要的理由就是对于一对(A, B)的取值,(f(n),f(n1))(f(n),f(n−1))的值对最多有49种,所以必然能在50步内找到循环节。由此形成如下代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 50;
int s[N];
int main(){
    int a, b, n;
    while(scanf("%d%d%d", &a, &b, &n), a+b+n){
        s[0] = s[1] = 1;
        int T = 49;
        for(int i = 2; i < N; i++){
            s[i] = (a*s[i-1]+b*s[i-2]) % 7;
            if(s[i] == 1 && s[i-1] == 1){
                T = i-1;
                break;
            }
        }
        printf("%d\n", s[(n+T-1)%T]);
    }
    return 0;
}

提交后,我们发现这份代码是AC的。但是有一组样例,这样的写法是明显错误的:

A=7x,B=7yA=7x,B=7y

此时
f(x)={1x=0,10x2f(x)={1x=0,10x≥2

这就不符合我们之上的理论了。

我们可以重新思考题意并建立模型,如果我们把任一对相邻的f(x)f(x)值看作一个点即 V={(f(i1),f(i))}V={(f(i−1),f(i))}那么我们可以知道任一vivi有且仅有一条可能指向自身的边,即f(a,b):vivjf(a,b):vi→vj。显然,这是一个49个点组成的图,且每个点有且仅有一条出边。

按直觉我们可以认定这个图必然有环,如果需要严谨证明的话,大概只能用到归纳法了:

若存在有n个点,每个点有一条出边的无环图,那么它必然是由n-1个点的具有同样性质的无环图产生的。(因为一个有环图不能通过增加一个点与一条点上的出边变得无环)

而已知当n=2时,不存在一个2个点,每个点均有一条出边的无环图。

所以有n个点,每个点有一条出边的图必有环。

那么重点即是,这个图中的环并不一定是从头开始,即(1,1)可能并不在循环节中:

Graph_1

知道以上结论后,我们可以将之上的理论进行代码实现:

#include <bits/stdc++.h>
using namespace std;
const int N = 10;
const int M = 50;
int fir[N][N], ans[M];
int main()
{
    int a, b, n;
    while(scanf("%d%d%d", &a, &b, &n), a+b+n){
        int x, y, cnt;
        x = y = cnt = 1;
        if(n == 1 || n == 2){
            printf("1\n");
            continue;
        }
        memset(fir, 0, sizeof(fir));
        memset(ans, 0, sizeof(ans));
        while(!fir[x][y]){
            fir[x][y] = cnt;
            ans[cnt++] = y;
            int v = (a*y + b*x)%7;
            x = y;
            y = v;
        }
        --n;
        if(n < fir[x][y])  printf("%d\n", ans[n]);
        else{
            int loop = cnt - fir[x][y];
            n -= fir[x][y];
            n %= loop;
            printf("%d\n", ans[n+fir[x][y]]);
        }
    }
    return 0;
}

即用fir数组来保存某一个值对第一次出现的计数,并为了我们方便地得到答案,使用ansiansi保存第i个答案。其中fir[x][y]保存的就是循环节的前导部分,loop为循环节长度:

Graph_2


此题还有另外一种解法,即O(logn)O(log⁡n)的矩阵快速幂的算法。

做过用矩阵快速幂求斐波那契数列的第n项的同学会知道,有下式:

(1110)(fn1fn2)=(fnfn1)(1110)∗(fn−1fn−2)=(fnfn−1)

即对于An=Tn1AiAn=Tn−1∗Ai,其中
T=(1110)T=(1110)

对于二次递推f(n)=Af(n1)+Bf(n2)+Cf(n)=A∗f(n−1)+B∗f(n−2)+C,有:

A10B00101fn1fn2C=fnfn1C(AB1100001)∗(fn−1fn−2C)=(fnfn−1C)

由于此题中C=0C=0,故本题中
T=(A1B0)T=(AB10)

代码为:
#include<bits/stdc++.h>
using namespace std;
const int N = 2;
struct Mat{
    int a[N][N];
    Mat(){memset(a, 0, sizeof(a));}
    Mat operator * (Mat& rh){
        Mat ret;
        for(int i = 0; i < N; i++)
            for(int j = 0; j < N; j++)
                for(int k = 0; k < N; k++){
                    ret.a[i][j] += a[i][k] * rh.a[k][j];
                    ret.a[i][j] %= 7;
                }
        return ret;
    }
};
Mat powMod(Mat a, int e){
    Mat ret;
    for(int i = 0; i < N; i++) ret.a[i][i] = 1;
    for(; e; e>>=1){
        if(e&1) ret = ret*a;
        a = a*a;
    }
    return ret;
}
int main()
{
    int a, b, n;
    while(scanf("%d%d%d", &a, &b, &n), a+b+n){
        Mat ans;
        ans.a[0][0] = a%7; ans.a[0][1] = b%7; ans.a[1][0] = 1;
        if(n<=2){
            printf("1\n");
            continue;
        }
        ans = powMod(ans, n-2);
        printf("%d\n", (ans.a[0][0]+ans.a[0][1])%7);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值