CSP历年复赛题-P1050 [NOIP2005 普及组] 循环

原题链接:https://www.luogu.com.cn/problem/P1050

题意解读:对于一个长达100为的整数n,计算在n的t次幂后,其后k位出现和初始情况一样,求最小的t,也就是循环长度。

解题思路:

首先,看到数据规模,此题肯定是高精度;

其次,如果直接采用模拟法,进行n*n,取后k位,高精度乘法的复杂度是100*100,判断后k位是否相等复杂度100,如果循环长度大于100,则会超时;

最后,一定要找到某种规律,不能直接模拟法,才能解得此题。

考试时,关键在于得分,我们一步一步来,先看看直接模拟法如何得分!

1、模拟法-非高精度

直接反复乘n,对后k位进行比较,一旦与初始相等,输出循环长度,否则超过一定次数还找不到,输出-1。

20分代码:

#include <bits/stdc++.h>
using namespace std;

int n, k;

int main()
{
    cin >> n >> k;
    int cnt = 0;
    int pow10k = 1; //10^k
    for(int i = 1; i <= k; i++) pow10k *= 10;
    int st = n % pow10k, ed = n; //st是初始后k位,ed记录n^t的结果后k位
    int times = 10000000;
    while(times--)
    {
        cnt++;
        ed = (ed * n) % pow10k;
        if(ed == st)
        {
            cout << cnt;
            return 0;
        } 
    }
    cout << -1;
    return 0;
}

2、模拟法-高精度

加上高精度,会不会多得一些分呢?

事实证明,在调整循环次数的情况下,艰难的多得了一分,没有比第一种好多少!

30分代码:

#include <bits/stdc++.h>
using namespace std;

vector<int> n;
int k;

vector<int> mul(vector<int> a, vector<int> b)
{
    vector<int> res(a.size() + b.size());
    for(int i = 0; i < k; i++) //高精度乘法只取后k位即可
    {
        for(int j = 0; j < k; j++) //高精度乘法只取后k位即可
        {
            if(i + j < k) //高精度乘法只取后k位即可
            {
                res[i + j] += a[i] * b[j];
                res[i + j + 1] += res[i + j] / 10;
                res[i + j] %= 10;
            }   
        }
    }
    return res;
}

int main()
{
    string str;
    cin >> str >> k;
    for(int i = 0; i < str.size(); i++)
        n.push_back(str[str.size() - 1 - i] - '0'); //高精度,个位在前

    int cnt = 0;
    int pow10k = 1; //10^k
    for(int i = 1; i <= k; i++) pow10k *= 10;
    vector<int> st(n), ed(n); //st是初始后k位,ed记录n^t的结果后k位
    int times = 10000;
    while(times--)
    {
        cnt++;
        ed = mul(ed, n); //不断乘n
        bool yes = true;
        for(int i = 0; i < k; i++) //判断后k位是否和初始相等
        {
            if(ed[i] != st[i])
            {
                yes = false;
                break;
            }
        }
        if(yes)
        {
            cout << cnt;
            return 0;
        } 
    }
    cout << -1;
    return 0;
}

3、递推-高精度

第二种方法的唯一问题是超时,主要原因就是要枚举n的每一次幂才行,有没有更简洁的方式呢?

可以假设后k-1位的循环长度已经计算出来,设为t,即n * n^t % 10^(k-1)和n % (k-1)相同

对于后k位如果要相同,必须有后k-1位相同,因此后k位的循环长度一定是t的倍数,有可能是1/2/3...s倍

即,n * n^t,n * n^2t, n * n^3t......某一个的后k位与n的后k位相同,又因为后k-1已经相同,所以只需要判断第k位是否相同

而第k位要相同,最多只需要10次枚举,因为整数只有0~9,如果超过10次还没有相同,则不存在循环。

根据以上分析,可以从最后1位、2位、3位的循环长度一直推出k位的循环长度。

举例:

数据 198123 4,因为要求只取后 4 位,所以将其截取成 8123

我们逐位进行处理:

  • 先处理最后一位的循环长度:最后一位是 3,在乘了4次8123后出现了循环,循环长度为 4。所以后两位的循环节长度一定为 4 的倍数,为了加快计算,我们可以将乘数变为 8123^4 ,取后 4 位变成 0641
  • 再处理后两位:后两位是 23 ,在乘了 5 次 0641 后出现了循环,循环节长度为 4*5=20。同样为了加快计算,乘数变为 8123^20=0641^5,取后 4 位变成 9201。之后就按照这样的方法处理即可。
  • 后三位:后三位是 123 ,乘了 5 次 9201 后出现循环,循环节长度为 20*5=100 ,乘数变为 9201^5%(10^4)=6001
  • 后四位:后四位是 8123 ,乘了 5 次 6001 后出现循环,循环节长度为 100*5=500,500 就是最终的答案。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 105;

vector<int> n;
int t[N]; //t[k]表示后k位的循环长度:后k-1位循环长度,t[0]=1
int k;

//高精度 * 高精度
vector<int> mul1(vector<int> a, vector<int> b)
{
    a.resize(k); //只取k位,提速
    b.resize(k); //只取k位,提速
    vector<int> res(a.size() + b.size());
    for(int i = 0; i < k; i++) //高精度乘法只取后k位即可
    {
        for(int j = 0; j < k; j++) //高精度乘法只取后k位即可
        {
            if(i + j < k) //高精度乘法只取后k位即可
            {
                res[i + j] += a[i] * b[j];
                res[i + j + 1] += res[i + j] / 10;
                res[i + j] %= 10;
            }   
        }
    }
    res.resize(k); //只取k位,提速
    return res; //不需要去除前导0,因为只取后k位即可
}

//高精度 * 低精度
vector<int> mul2(vector<int> a, int b)
{
    vector<int> result;
    int t = 0; // 进位
    for(int i = 0; i < a.size() || t; i++) //这里把对最后一个t不为0的后续操作合并到一起
    {
        if(i < a.size()) t += a[i] * b; //合并之后需要考虑i超过a的范围
        result.push_back(t % 10);
        t /= 10;
    }
    return result;
}


int main()
{
    string str;
    cin >> str >> k;
    for(int i = 0; i < str.size(); i++)
        n.push_back(str[str.size() - 1 - i] - '0'); //高精度,个位在前

    t[0] = 1; //最终结果是所有t[i]之积
    vector<int> p(n); //乘数,初始是n
    for(int i = 1; i <= k; i++) //依次计算后1~k位的循环长度
    {
        //乘数变成p^t[i-1],存入q
        vector<int> q(1, 1); //累计乘p的结果
        for(int j = 1; j <= t[i-1]; j++) q = mul1(q, p);
        p = q;
       
        //计算后i位循环长度与后i-1位循环长度的比值t[i]
        vector<int> res(n); //n累计乘q,记录循环次数,使得第i位与n第i位相等
        for(int j = 1; j <= 10; j++)
        {
            res = mul1(res, q);
            //判断第i位是否相等,因为前i-1位已经相等
            if(res[i-1] == n[i-1])
            {
                t[i] = j; //后i位的循环长度是j倍的i-1位的循环长度
                break;
            }
        }
        if(t[i] == 0) //没有找到后i位的循环长度
        {
            cout << -1; 
            return 0;
        }
    }    
    vector<int> ans(1,1);
    for(int i = 1; i <= k; i++) ans = mul2(ans, t[i]); //结果是所有t[i]乘积
    for(int i = ans.size() - 1; i >= 0; i--) cout << ans[i];
    
    return 0;
}

 

posted @ 2024-05-24 13:31  五月江城  阅读(114)  评论(0编辑  收藏  举报