数列(循环节)

题意

有一个整数数列\(a_0, a_1, a_2, a_3 \dots\)

该数列的前两项\(a_0, a_1\)的具体值已知,其它项可以通过如下递推式求出:

  • \(a_n = p \times a_{n−1} + q \times a_{n−2}(n \geq 2)\)

给定一个可能非常大的正整数\(N\)和两个不太大的正整数\(M, K\),请你分别计算并输出\(a_{N+1}, a_{N+2}, \dots, a_{N+K}\)\(M\)取模的值。

题目链接:https://www.acwing.com/problem/content/4584/

数据范围

\(0 < a_0, a_1, p, q \leq 32693\)
\(0 < M \leq 3 \times 10^3\)
\(0 < K \leq 10^4\)
\(0 < N < 10^{10^6}\)

思路

首先我们观察到\(M\)的范围较小,只有\(3 \times 10^3\),因此数列中的数最多有\(3 \times 10^3\)个取值。

\(N\)特别大,这种题目的典型套路是分析循环节。

那么什么时候才会出现循环呢?如果\(a_{i-1}\)\(a_i\)在之前出现过,即存在\(j < i\)满足\(a_{j-1} = a_{i-1}\)并且\(a_j = a_i\)。那么就出现循环了。其中循环节的长度为\(r = i-j\)

因此,我们只需要计算出\(N\)位于循环节的哪个位置即可,即:\((n - (j - 1)) \% r = n \% r - (j - 1) \% r\)

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e7 + 10, M = 3010;

int a[N], p, q;
int m, k;
int id[M][M];
string n;

int main()
{
    cin >> a[0] >> a[1] >> p >> q >> m >> k >> n;
    a[0] %= m, a[1] %= m;
    id[a[0]][a[1]] = 1;
    int ed, st;
    for(int i = 2; i < N; i ++) {
        a[i] = (a[i - 1] * p % m + a[i - 2] * q % m) % m;
    }
    for(int i = 2; i < N; i ++) {
        if(id[a[i - 1]][a[i]]) {
            ed = i - 2;
            st = id[a[i - 1]][a[i]] - 1;
            break;
        }
        id[a[i - 1]][a[i]] = i;
    }
    int len = ed - st + 1;
    if(n.size() <= 7) {
        int num = 0;
        for(int i = 0; i < n.size(); i ++) {
            num = num * 10 + n[i] - '0';
        }
        for(int i = 1; i <= k; i ++) {
            cout << a[num + i] << '\n';
        }
    }
    else {
        int num = 0;
        for(int i = 0; i < n.size(); i ++) {
            num = (num * 10 % len + n[i] - '0') % len;
        }
        int r = (num - st % len + len) % len;
        for(int i = st + r + 1; i <= st + r + k; i ++) {
            cout << a[i] << '\n';
        }
    }
    return 0;
}
posted @ 2022-08-16 10:08  pbc的成长之路  阅读(142)  评论(0编辑  收藏  举报