【C003】LJX的校园:入学典礼【难度C】——————————————————————————————————————————————————————————————————————————————————————
【题目要求】
LJX上中学啦!他与YSM,YSF,WHT,LTJ等人都是校友。今天,是他人生中“溺亡”的一天。今天,他要向同学们证明他的数学很“乐呵”。 于是,刚学会简单的A+B问题的他,在课上,向冤 家对头 斯沃琪 挑战 QAQ,斯沃琪 队有YZM,SJY,ZZQ等人。而LJX队有他的好朋(ji)友:YSM,YSF,WHT,LTJ,LZH等人,实力不弱小觑。有这么一道题:
给定正整数N,M,要求计算1,2,……,N连接起来(1234567891011……N)mod M的值。
“新兵”LJX想了想,要是M是3,或者9,他一定会。但是M什么都可以,只要小于INF。“预约”了各种方法以后,费劲脑筋想不出来。于是,右转向了(呵呵)他的(ZHU)队友们。可他们已经跑了 TAT。jue ruo LJX找到了你,他跪求你编一个程序帮他解决问题。否则,他将在毕业典礼上“锻炼”,并且被可怕的斯沃琪虐残 QAQ
【输入要求】
* 一行:正整数N,M。
【输入示例】
输入样例1 13 13 输入样例2 12345678910 1000000000
【输出要求】
* 一行:按要求输出
【输出示例】
输出样例1 4 输出样例2 345678910
【其它要求】
N<=10^18
M<=10^9
这数据是在坑LJX呀
【试题分析】对于连线段树都用不熟练的“键人”蒟蒻(不是魔芋)来说,看到其它要求瞬间蒙逼,最后花了10^1000000000纳秒终于发现了这道题简直就是连改都没改的矩阵快速幂,于是直接上模板。
矩阵快速幂大家都应该很熟悉了下面我来讲一下它的基本原理:两矩阵相乘,一般的算法(QAQ)的复杂度是O(N^3)。如果求一次矩阵的M次幂,按朴素的写法就O(N^3*M)。既然是求幂,不免想到快速幂取模的算法,有了快速幂取模的,a^b %m 的复杂度可以降到O(logb)。如果矩阵相乘是不是也可以实现O(N^3 * logM)的时间复杂度呢?答案是肯定的。我们可以
把n个矩阵进行两两分组,比如:A*A*A*A*A*A => (A*A)*(A*A)*(A*A)这样变的好处是,你只需要计算一次A*A,然后将结果(A*A)连乘自己两次就能得到A^6,即(A*A)^3=A^6。算一下发现这次一共乘了3次,少于原来的5次。其实大家还可以取A^3作为一个基本单位。原理都一样:利用矩阵乘法的结合律,来减少重复计算的次数。以上都是取一个具体的数来作为最小单位的长度,这样做虽然能够改进效率,但缺陷也是很明显的,取个极限的例子(可能有点不恰当,但基本能说明问题),当n无穷大的时候,你现在所取的长度其实和1没什么区别。所以就需要我们找到一种与n增长速度”相适应“的”单位长度“,那这个长度到底怎么去取呢???这点是我们要思考的问题。有了以上的知识,我们现在再来看看,到底怎么迅速地求得矩阵的N次幂。我们可以进行离散化(其实我也不懂)下面是我转载的
大家首先要认识到这一点:任何一个整数N,都能用二进制来表示。。这点大家都应该知道,但其中的内涵真的很深很深(这点笔者感触很深,在文章的最后,我将谈谈我对的感想)!!
计算机处理的是离散的信息,都是以0,1来作为信号的处理的。可想而知二进制在计算机上起着举足轻重的地位。它能将模拟信号转化成数字信号,将原来连续的实际模型,用一个离散的算法模型来解决。 好了,扯得有点多了,不过相信这写对下面的讲解还是有用的。
回头看看矩阵的快速幂问题,我们是不是也能把它离散化呢?比如A^19 => (A^16)*(A^2)*(A^1),显然采取这样的方式计算时因子数将是log(n)级别的(原来的因子数是n),不仅这样,因子间也是存在某种联系的,比如A^4能通过(A^2)*(A^2)得到,A^8又能通过(A^4)*(A^4)得到,这点也充分利用了现有的结果作为有利条件。下面举个例子进行说明:
现在要求A^156,而156(10)=10011100(2) 也就有A^156=>(A^4)*(A^8)*(A^16)*(A^128) 考虑到因子间的联系,我们从二进制10011100中的最右端开始计算到最左端。细节就说到这。
【代码】
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define MAXN 4 using namespace std; typedef long long int LL; int mod; struct matrix { LL p[MAXN][MAXN]; }ans,tmp; matrix operator*(matrix a,matrix b) { matrix c; for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) { c.p[i][j]=0; for(int k=1;k<=3;k++) c.p[i][j]=(c.p[i][j]+((a.p[i][k]%mod)*(b.p[k][j]%mod))%mod)%mod; } return c; } void cal(LL t,LL last) { memset(tmp.p,0,sizeof(tmp.p)); tmp.p[1][1]=t; tmp.p[2][1]=tmp.p[3][1]=tmp.p[2][2]=tmp.p[3][2]=tmp.p[3][3]=1; LL y=last-t/10+1; while(y) { if(y&1) ans=ans*tmp; tmp=tmp*tmp; y>>=1; } } int main() { for(int i=1;i<=3;i++) ans.p[i][i]=1; LL n; scanf("%lld%lld",&n,&mod); LL t=10; while(n>=t) { cal(t,t-1); t*=10; } cal(t,n); printf("%lld\n",ans.p[3][1]); return 0; }
我的感想:艰苦的学完快速幂之后,每次比赛把那么一大长串的思想重新想一遍肯定是不行的,所以我们一定要背模板!背模板!背模板!(重要的事情说三遍)要做到拿来就能写。