「NOIP2005P」循环

问题描述

       对于一个整数n的正整数次幂来说,它的后k位是否会发生循环?如果循环的话,循环长度是多少呢?

输入格式
  只有一行,包含两个整数n(1 <= n < 10^100)和k(1 <= k <= 100),n和k之间用一个空格隔开,表示要求n的正整数次幂的最后k位的循环长度。
 
输出格式
  包括一行,这一行只包含一个整数,表示循环长度。如果循环不存在,输出-1。
 
数据规模和约定
  对于30%的数据,k <= 4;
  对于全部的数据,k <= 100。
 
解题算法
【模拟】
 
解题历程:
 
先暴力切掉30分
 
大致是维护一个k位字符串不断乘上一个int型,
出现与刚开始如出一辙的串式可以判断是出现循环了,输出;
出现了出现过的串式(刚开始的除外)就可以看出是死循,输出(-1)
【可能在数学上有更好的判断方式,但目前只能想到这样】
 
至于判重,用了字符串哈希,膜一个大素数即可。
 
就是一个高精度乘法
代码:

void check()
{
    int rest=0;
    F(i,0,k-1)
    {
        int sub=(st[i]-'0')*n+rest;
        st[i]=sub%10+'0';
        rest=sub/10;
    }
    return;
}

int change(string st)
{
    int x=0;
    F(i,0,k-1)
    x=(x*10+st[i]-'0')%mo;
    return x;
}

void work()
{
    int cnt=0;
    while(1)
    {
        cnt++;
        check();
        if(st==ansst)
        {
            cout<<cnt<<endl;
            return;
        }    
        int num=change(st);
        if(vit[num]==1)
        {
            cout<<"-1"<<endl;
            return;
        }
        else vit[num]=1;
    }
    return ;
}
以上、
这样30分。
 
看了下数据,n远超Int 和ll 型,
就码了一个真~高精度乘法
好像还是第一次码...很坎坷
 
代码:
void check()
{
    memset(sum,0,sizeof(sum));
    int rest=0;
    int len=n.size();
    F(t,0,len-1)
    {
        rest=0;
        F(i,0,k-1-t)
        {
            int sub=(st[i]-'0')*(n[t]-'0')+rest;
            sum[t+i]+=sub%10;
            rest=sub/10;
        }
    }
    rest=0;
    F(i,0,k-1)
    {    
        int sub=sum[i]+rest;
        st[i]=sub%10+'0';
        rest=sub/10;    
    }
    return;
}
以上、
 
写的时候主要是字符类型和整数转换出了问题
改了半天
 
可能是写的有问题,分数上没有丝毫增长......
优化解法今天头疼不想了,
下次再写。
 
以上
2018.2.12
 
--------------------------------------------------
今天尝试把字符串哈希改成十进制的
把取膜数微调一下
分数没有上涨
所以可能是算法的问题
改了一个更简朴的,即直接记录串式,每次跟前面比较
 
代码:
    int cnt=0;
    while(1)
    {
        cnt++;
        check();
        if(st==ansst[0])
        {
            cout<<cnt<<endl;
            return;
        }   
        int fflag=0;
        F(i,1,cnt_st)
        if(st==ansst[i])
        {
            fflag++;
            break;
        }
        if(fflag)
        {
            cout<<"-1"<<endl;
            return;
        }
        else ansst[++cnt_st]=st;
    }
以上、
TLE止于30
 
毕竟每次记录时间复杂度太大
之前哈希策略反而显得更优秀
思而不得
 
以上
2018.2.13
-------------------------------------------
 
归来
之前算法注定爆
复杂度高+hash风险

草稿纸画了画
想出了新算法
 
清晰可见的是
后k位相等得数
最后1,2,3...k位都等
 
即k位循环一定出现在最后1,2,3...k-1位的循环上
 
这就很清晰了
不断乘(一定在9次以内)
第一次出现末尾等即出现了循环【1】
 
那么循环【2】一定是多个循环【1】拼接而成
 
也就是说如果n*x=n[1],n*y=n[2]
那么n[2]一定是n*x^a,或者说n[2]=n[1]*x^a-1
即y=x^a
 
可见需要记两个大整数 x 和n[1];
 
代码:
void work(int ist)
{
    if(ist==k)
    {
        int sub=k-1;
        while(ans[sub]=='0'&&sub!=0)sub--;
        D(i,sub,0)cout<<ans[i];
        cout<<endl;
        return;
    }
    int aloncnt=0,sum=0;
    while(1)
    {
        //cout<<ist<<" "<<aloncnt<<" "<<subn<<" "<<st<<endl;
        //cout<<ans<<endl;
        if(aloncnt>9||(flag==true&&st[ist]==subn[ist]))break;
//之所以先判断是巧妙地处理了n[a]=n[a+1]的情况 ,fllag是消除初始数与自身比较的bug
        st=check(st,n);
        anoy=check(anoy,n);
        ans=check_add(ans,last);
        flag=true;
        aloncnt++;
    }
    if(aloncnt>9)
    {
        cout<<"-1"<<endl;
        return ;
    }
    last=ans;
    if(aloncnt)n=anoy;
    work(ist+1);
}
 
以上
AC
 
当然了
还有一个难点
 
即记录ans值
我当时对着数据没想清楚
 
回家拿笔算了算
大概就明了了
 
即 当前将n[q]*x[q]^a[q]得到循环
那么那么这次ans增添的值就是a[q]*上一个时刻的ans(用心悟能读懂)
 
over
以上
2018.3.4
posted @ 2018-03-04 12:46  Srzer  阅读(186)  评论(0编辑  收藏  举报