「NOIP2005P」循环
问题描述
对于一个整数n的正整数次幂来说,它的后k位是否会发生循环?如果循环的话,循环长度是多少呢?
输入格式
只有一行,包含两个整数n(1 <= n < 10^100)和k(1 <= k <= 100),n和k之间用一个空格隔开,表示要求n的正整数次幂的最后k位的循环长度。
输出格式
包括一行,这一行只包含一个整数,表示循环长度。如果循环不存在,输出-1。
数据规模和约定
对于30%的数据,k <= 4;
对于全部的数据,k <= 100。
对于全部的数据,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;
}
{
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;
}
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;
{
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);
}
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