XUJCOJ:caiming:2022级C++第19次作业 第9题
第9题 描述 有一个整数数列,第一项是a,以后的每一项都是前一项乘以b再加c的结果,求数列第m项的后两位数字(即十位数和个位数) 输入 一个正整数n,表示有n组测试案例。 每组案例由4个正整数a、b、c、m组成(a、b、c均小于1e+7,m小于1e+9) 输出 针对每组案例,输出一个整数,表示数列第m项的后两位数字。 每组案例输出完都要换行。 样例输入 1 12 3 5 3 样例输出 28 思路 用dzt都会写的“朴实无华”方法可以通过大概40~50%的数据,但暴力方法要拿到AC必定会遇到黄色超时的阻挠 既然一般的方法又不行,那我们就再看看二般的方法 回到计算机底层对数字处理的方式,我们可以知道,当我们输入一个10进制数字,计算机虽然执行指令时还是需要执行2进制指令,具体的次数则由我们对循环的定义进行 举个例子,当我们定义一个for循环时,如果我们让他循环10次,那么计算机也就是按序循环10次,但如果我们通过一些已知的原则或是规律,就能从根源上起到“不用循环到10次”的效果 这里就需要介绍一下一种叫做“快速幂”的算法,众所周知,7的10次方可以表示为10个7相乘。这里如果我们按着这个表述去实现我们的程序,那么这个程序就需要执行10次7的相乘。但如果我们知道,7的10次方可以表示为7的平方和7的5次方相乘,那么我们的循环次数就是2+5=7次,这样我们就可以不用循环10次也能够得到正确的结果.以此类推的,我们可以尽可能想出执行次数少的方法来得到正确结果。而这一思想的宗旨就是去拼凑出“大底数小指数”的组合。 我们将这种方法和位运算结合,就可以得到以超大值m为幂的值的结果。为什么呢?如果你知道2进制与10进制的转换方式,其实很简单,当m每次右移1时,如果该位是1,那么计数器ans就乘上b的该位次序的次方。而根据int的性质,我们可以知道,这样的计算方法将计算次数压缩成小到极致的小于等于32次。 下面我们再来分析一下数学公式。 原始代数式是(…(((a_b+c)_b+c)b+c)…b+c) 经过展开后可以得到a_b^(m-1)+c(从1到b^(m-1)的等比数列) 首先,我们可以根据上面的方法算出a_b^(m-1)的值,接着再通过等比数列的数学性质来解决后面的值,也就是1+b+b^2+b^3+…b^(m-1)=(1+b^((m-1)/2))(1+b+b^2+b^3+…b^((m-1)/2)).【如果m是奇数,那么就会有偶数项,所有前面m-1就要改成m-2,再在最后加个b^(m-1)】 不废话,下面上代码 代码 [Forlogin] #include <bitset> using namespace std; typedef long long ll; int MOD=10000; ll quick(ll a,ll b) { ll ans=1; a%=MOD; while(b) { if(b&1) ans=ans*a%MOD; a=a*a%MOD; b>>=1; } return ans%MOD; } ll sum1(ll a,ll b) { if(b==0) return 1; if(a==0) return 0; if(b&1) return ((1+quick(a,b/2+1)) * sum1(a,b/2))%MOD; else return ((1+quick(a,b/2+1)) * sum1(a,b/2-1) + quick(a,b/2))%MOD; } int main(){ int n; scanf("%d",&n); while (n--){ ll a,b,c,m,num; scanf("%lld%lld%lld%lld",&a,&b,&c,&m); if (m==1)printf("%lld\n",a%100); else { num=((a* quick(b,m-1))+c*(sum1(b,m-2)))%100; printf("%lld\n",num); } } return 0; }[/Forlogin]