[HNOI2011]数学作业
题目描述
小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题:
给定正整数 \(n,m\),要求计算 \(\text{Concatenate}(n) \bmod \ m\) 的值,其中 \(\text{Concatenate}(n)\) 是将 \(1 \sim n\) 所有正整数 顺序连接起来得到的数。
例如,\(n = 13\) , \(\text{Concatenate}(n) = 12345678910111213\)。小C 想了大半天终于意识到这是一道不可能手算出来的题目,于是他只好向你求助,希望你能编写一个程序帮他解决这个问题。
输入格式
一行两个正整数 \(n,m\)。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
13 13
样例输出 #1
4
提示
【数据范围】
对于 \(30\%\) 的数据,\(1\le n \le 10^6\);
对于 \(100\%\) 的数据,\(1\le n \le 10^{18}\),\(1\le m \le 10^9\)。
思路
观察这个\(Concatenate\)数,不难得到递推式:
\[Concatenate[i]=Concatenate[i-1]\times10^{len(i)}+i
\]
其中\(len(i)\)为\(i\)的位数。
\(100\%\)数据是\(1\le n \le 10^{18}\),暴力推肯定不行,考虑矩阵加速。
设状态矩阵为\(\begin{bmatrix} F[i] & i & 1\end{bmatrix}\),它应该从\(\begin{bmatrix} F[i-1] & i-1 & 1\end{bmatrix}\)转移过来。因此可得转移矩阵为\(\begin{bmatrix} 10^{len(i)}&0&0\\1&1&0 \\1&1&1\end{bmatrix}\)。每次\(i\)的位数增加的时候更新\(len(i)\)的值即可。
代码
#include<cstdio>
#include<iostream>
#define int long long
using namespace std;
int n,mod,num,r,k,kk;
struct matrix
{
int mat[4][4],n,m;
matrix(int a,int b)
{
n=a,m=b;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
mat[i][j]=0;
}
void set_i()
{
for(int i=1;i<=n;i++)
mat[i][i]=1;
}
matrix operator *(matrix b)
{
matrix ans(n,b.m);
for(int i=1;i<=n;i++)
for(int j=1;j<=b.m;j++)
for(int k=1;k<=m;k++)
ans.mat[i][j]=(ans.mat[i][j]+(mat[i][k]*b.mat[k][j])%mod)%mod;
return ans;
}
};
matrix qpow(matrix a,int b)
{
matrix ans(a.n,a.m);
ans.set_i();
while(b)
{
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
int numb(int x)
{
int ret=0;
while(x)
{
x/=10;
ret++;
}
return ret;
}
signed main()
{
scanf("%lld%lld",&n,&mod);
num=numb(n);
matrix f(1,3);
f.mat[1][1]=f.mat[1][2]=f.mat[1][3]=1;
matrix bb(3,3);
bb.mat[1][1]=10;bb.mat[2][1]=bb.mat[2][2]=bb.mat[3][1]=bb.mat[3][2]=bb.mat[3][3]=1;
matrix pro(3,3);
pro.set_i();
int mi=9,now=0;
for(int i=1;i<num;i++)
{
now+=mi;
if(i==1)pro=pro*qpow(bb,mi-1);
else pro=pro*qpow(bb,mi);
bb.mat[1][1]=(bb.mat[1][1]*10)%mod;
mi=(mi<<1)+(mi<<3);
}
if(num==1)pro=pro*qpow(bb,n-now-1);
else pro=pro*qpow(bb,n-now);
f=f*pro;
printf("%lld",f.mat[1][1]%mod);
return 0;
}