洛谷P3216 [HNOI2011]数学作业
题目描述
小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题:
给定正整数 N 和 M,要求计算 Concatenate (1 .. N) Mod M 的值,其中 Concatenate (1 ..N)是将所有正整数 1, 2, …, N 顺序连接起来得到的数。例如,N = 13, Concatenate (1 .. N)=12345678910111213.小C 想了大半天终于意识到这是一道不可能手算出来的题目,于是他只好向你求助,希望你能编写一个程序帮他解决这个问题。
输入输出格式
输入格式:
从文件input.txt中读入数据,输入文件只有一行且为用空格隔开的两个正整数N和M,其中30%的数据满足1≤N≤1000000;100%的数据满足1≤N≤1018且1≤M≤109.
输出格式:
输出文件 output.txt 仅包含一个非负整数,表示 Concatenate (1 .. N) Mod M 的值。
输入输出样例
题解:想想看,如果n范围比较小时你会怎么做?
我们应该想到,比如说对于序列1234567,要取模的数是13,那么我们会先让1对13取模得到1,再让12对13取模得到12,再让123对13取模得到6,再让64对13取模得到12,再让125对13取模得到8,再让86对13取模得到8,最后让87对13取模得到9。这与1234567直接对13取模的值是相同的。其中的数学证明不再赘述。
于是我们可以得到递推式,对于序列f(n),f(n)=f(n-1)×10^len(n)+n(其中len(n)表示n这个数字有几位)。
由于n的范围极大,我们考虑用矩阵快速幂转移优化,构造矩阵如下:
f(n) 10^len(n) 1 1 f(n-1)
n = 0 1 1 × n-1
1 0 0 1 1
我们转移时,根据1~9时len(i)相同,10~99时len(i)相同,100~999时len(i)相同划分为不同的区块进行转移优化。
这个转移思路是从WHC大佬那里获得的,本蒟蒻还是太弱了。
接下来还是看代码吧。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int N=3; 6 struct node{ 7 long long a[N][N]; 8 void clr(){memset(a,0,sizeof(a));} 9 }ans,p[20]; 10 long long n,mod,now,t; 11 node operator * (node x,node y) 12 { 13 node z; z.clr(); 14 for (int i=0;i<3;++i) 15 for (int j=0;j<3;++j) 16 for (int k=0;k<3;++k) 17 z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j])%mod; 18 return z; 19 } 20 node Pow(node x,long long y) 21 { 22 node tmp=x; 23 for (;y;y>>=1,x=x*x) if (y&1) tmp=tmp*x; 24 return tmp; 25 } 26 long long calc(long long x,long long y) 27 { 28 long long tmp=1; 29 for (;y;y>>=1,x=x*x) if (y&1) tmp=tmp*x; 30 return tmp; 31 } 32 int main() 33 { 34 scanf("%lld%lld",&n,&mod); 35 ans.clr(); ans.a[2][0]=1; 36 p[0].clr(); p[0].a[0][0]=p[0].a[0][1]=p[0].a[0][2]=p[0].a[1][1]=p[0].a[1][2]=p[0].a[2][2]=1; 37 for (int i=1;i<=19;++i) p[i]=p[i-1],p[i].a[0][0]*=10,p[i].a[0][0]%=mod; 38 for (long long i=0,j=9,k=0;k<min(n,(long long)999999999999999999);k+=9*calc(10,i),++i) 39 ans=Pow(p[i+1],min(n-k,9*calc(10,i))-1)*ans; 40 if (n==1000000000000000000) ans=p[19]*ans; 41 printf("%lld ",ans.a[0][0]); 42 return 0; 43 }