noip2005普及组 循环

题目

循环

算法

(高精度,数学,数论,递推) O ( 10 k 3 ) O(10k^3) O(10k3)

引理1: 前 k + 1 k+1 k+1 位的所有循环节长度,一定是 前 k k k 位循环节长度的子集。

证明:

  • 如果前 k k k 位都不相同,那么前 k + 1 k+1 k+1 位一定也不相同。

引理2: 假设最小循环节长度是 t t t,那么所有循环节长度一定都是 t t t 的整数倍。

证明:

  • 假设有循环节长度 r r r 不是 t t t 的倍数,那么令 p = r   m o d   t p=r \, mod \, t p=rmodt ,则 p < t pp<t,且对于任意正整数 a a a,均有 n a ≡ n a + r ≡ n a + r − t ≡ n a + r − 2 t ≡ … ≡ n a + p   m o d   1 0 k na≡na+r≡na+r−t≡na+r−2t≡…≡na+p \, mod\, 10^k nana+rna+rtna+r2tna+pmod10k,所以 p p p 也是循环节,与 t t t 是最小循环节矛盾。

因此我们可以从小到大一位一位递推出前 k k k 位的循环节长度。

假设已经求出前 k 0 k_0 k0 位的循环节长度 t 0 t_0 t0,那当我们求前 k 0 + 1 k_0+1 k0+1 位的循环节长度时,
只需枚举 n × n t 0 , n × n 2 t 0 , n × n 3 t 0 , … , n × n 10 t 0 n×n^{t_0},n×n^{2t_0},n×n^{3t_0},…,n×n^{10t_0} n×nt0,n×n2t0,n×n3t0,,n×n10t0 是否和 n n n 相同即可。

注意这里只需要枚举前10项,因为前 k 0 k_0 k0 位是固定不变的,只有第 k 0 + 1 k_0+1 k0+1 位会变化,因此最多只有10种不同选择。

时间复杂度

总共需要递推 k k k 位,每次递推时最多需要枚举10项,每次枚举时会用到高精度乘法。

这里高精度乘法没有用FFT加速,因此时间复杂度是 O ( 10 k 3 ) O(10k^3) O(10k3)

C++ 代码

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

using namespace std;

const int N = 110;

int m;
int nums[N], power[N]; // power存的是n^L_{k-1}

// 高精度乘法
void mul(int c[], int a[], int b[])
{
    static int temp[N];
    memset(temp, 0, sizeof temp);
    for (int i = 0; i < m; i++)
        for (int j = 0; j < m; j++)
            if (i + j < m)
                temp[i + j] += a[i] * b[j];

    for (int i = 0, t = 0; i < m; i++)
    {
        t += temp[i];
        temp[i] = t % 10;
        t /= 10;
    }

    memcpy(c, temp, sizeof temp);
}

// 高精度乘低精度
void mul(int c[], int a[], int b)
{
    for (int i = 0, t = 0; i < m; i++)
    {
        t += a[i] * b;
        c[i] = t % 10;
        t /= 10;
    }
}

int main()
{
    string str;
    cin >> str >> m;
    for (int i = 0, j = str.size() - 1; j >= 0; i++, j--)
        nums[i] = str[j] - '0'; // 把所有的数存入nums里

    memcpy(power, nums, sizeof nums);

    int per[N] = {1}; // per存周期长度
    int pn[N], p1[N];// pn存n*n^iL_{k-1},p1存1*n^iL_{k-1}
    for (int k = 1; k <= m; k++)
    {
        memcpy(pn, nums, sizeof pn); // 把 n copy到pn里
        memset(p1, 0, sizeof p1); // p1初始化
        p1[0] = 1;

        // 求一下当前的循环节是多少
        int r = 0;
        while (r <= 10)
        {
            mul(pn, pn, power); // pn = pn * power
            mul(p1, p1, power); // p1 = p1 * power
            r++;
            if (pn[k - 1] == nums[k - 1])
                break;
        }

        memcpy(power, p1, sizeof p1);

        if (r > 10)
        {
            memset(per, 0, sizeof per);
            per[0] = -1;
            break;
        }

        mul(per, per, r); // per = per * r
    }

    int k = m;
    while (!per[k])
        k--; //把前面的0去掉

    while (k >= 0)
        cout << per[k--]; // 从高到低把所有位输出

    return 0;
}

你可能感兴趣的:(oi)