「HNOI 2011」数学作业

Description

题目链接:BZOJ 2326

小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题:

给定正整数 $n$ 和 $m$,要求计算 $\text{Concatenate}(1\dots n)\bmod m$ 的值,其中 $\text{Concatenate}(1\dots n)$ 是将所有正整数 $1,2,\dots,n$ 顺序连接起来得到的数。小 C 想了大半天终于意识到这是一道不可能手算出来的题目,于是他只好向你求助,希望你能编写一个程序帮他解决这个问题。

数据范围:$1\le n\le 10^{18}$,$1\le m\le 10^9$


Solution

我们设前 $i$ 个数的答案为 $f_i$,那么可以列出如下转移方程:

发现第 $i$ 项之和第 $i-1$ 项有关,可以考虑矩阵快速幂优化递推。

转移矩阵为:

但是我们发现,指数 $k$ 的值为 $\left\lfloor\log_{10}i\right\rfloor+1$(也就是 $len(i)$)的值是会变化的,没法直接矩乘。由于 $len(i)$ 的值一共只有 $18$ 种左右,所以我们直接枚举 $len(i)$ 然后分块矩乘后合并。

注意:合并时,必须用转移矩阵的若干次方左乘答案矩阵!

时间复杂度:$O(3^3\log^2 n)$


Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <cstdio>
#include <cstring>
#include <algorithm>

const int N=4;
int mod;

void upd(int &x,int y) {
(x+=y)>=mod&&(x-=mod);
}
struct Matrix {
int n,A[N][N];
Matrix(int _n=0) {n=_n,memset(A,0,sizeof(A));}
void operator ~ () {
for(int i=0;i<n;++i) A[i][i]=1;
}
Matrix operator * (const Matrix &b) const {
Matrix ret(n);
for(int i=0;i<n;++i) for(int j=0;j<n;++j) for(int k=0;k<n;++k) {
upd(ret.A[i][k],1LL*A[i][j]*b.A[j][k]%mod);
}
return ret;
}
Matrix operator ^ (const long long &b) const {
Matrix ret(n),x=*this; ~ret;
for(long long p=b;p;p>>=1,x=x*x) if(p&1) ret=ret*x;
return ret;
}
};

Matrix ans(3);
void calc(long long p,long long b) {
Matrix x(3);
x.A[0][0]=p%mod,x.A[0][1]=x.A[0][2]=x.A[1][1]=x.A[1][2]=x.A[2][2]=1;
ans=(x^b)*ans;
}
int main() {
long long n;
scanf("%lld%d",&n,&mod);
~ans;
long long r=10;
while(r<=n) calc(r,r-(r/10)),r*=10;
calc(r,n-r/10+1);
printf("%d\n",ans.A[0][2]);
return 0;
}
0%