【HNOI2011】数学作业
题目描述
小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题:
给定正整数 N 和 M ,要求计算Concatenate(1..N) Mod M 的值,其中 Concatenate(1..N) 是将所有正整数 1,2,…,N 顺序连接起来得到的数。例如,N=13 , Concatenate(1..N)=12345678910111213 .小C 想了大半天终于意识到这是一道不可能手算出来的题目,于是他只好向你求助,希望你能编写一个程序帮他解决这个问题。
分析
彩笔还是没有思路啊~
其实本题用到的思路是,当你求一个数对某一个值取模的时候,可以按位取模,即从高位向低位求这个数的过程中不断取模。可是这样的话,复杂度就是O(n)了,n是1e18的,我勒个去。
嘿嘿,递推过不去?上矩阵快速幂啊!不过这道题有点小麻烦,你列出式子,f【n】=f【n-1】*10^k+n,k是n的位数。矩阵加速递推的时候,系数必须是固定不变的,现在这样的话,我们无法进行递推。所以需要对数进行分块处理,每次处理相同位数的数,做一次矩阵快速幂。还有就是把n拆成n-1+1,这样就和f【n-1】保持一致了。设计1*3的状态矩阵与3*3的状态转移矩阵,做完快速幂之后f【0】【0】就是答案。
这道题比较神奇的是两个数相乘之前一定取模,没取模就奇怪的Wa了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; ll n,mod,t,A[3][3],f[3][3]; void mul(ll a[3][3],ll b[3][3],ll c[3][3]){ ll tmp[3][3]={}; for(int i=0;i<3;++i) for(int j=0;j<3;++j) for(int k=0;k<3;++k) tmp[i][j]=(tmp[i][j]+(a[i][k]%mod)*(b[k][j]%mod))%mod; for(int i=0;i<3;++i) for(int j=0;j<3;++j) c[i][j]=tmp[i][j]; } void calc(ll t,ll last){ memset(A,0,sizeof(A)); A[0][0]=t;A[1][1]=1; A[2][2]=1;A[1][0]=1; A[2][0]=1;A[2][1]=1; ll y=last-t/10+1; for(;y;y>>=1){ if(y&1) mul(f,A,f); mul(A,A,A); } } int main(){ scanf("%lld%lld",&n,&mod); if(mod==1){ printf("0\n"); return 0; }else{ f[0][2]=1; t=10; while(n>=t){ calc(t,t-1); t*=10; } calc(t,n); } printf("%lld\n",f[0][0]); return 0; }