巨大的数(dp+矩阵加速)
小明定义了一种生成大数的函数f[n],他的含义是将1-n所有的正整数按照从小到大拼接起来,形成一个巨大的数,例如f[13]=12345678910111213,现在给定一个数n,输出f[n]%m的值,其中n和m都是正整数
输入格式
第一行两个整数n,m
部分数据:1<=n<=1e6
全部数据:1<=n<=1e18,1<=m<=1e9
输出格式
一个整数
输入/输出例子1
输入:
13 13
输出:
4
样例解释
无
当要算矩阵时,某个因数的系数(例如5a的系数是5)发生了有规律性的变化,那么考虑分段处理
先考虑朴素dp
f(i): 前i个数拼起来,%m是多少
由f(i-1)转移
举个例分析下
假设有一串数,以i-1结尾,且f[i-1]=x,可以这样表示:
(1234567...i-1)%m=x
令y=(1234567...i-1)
在这一串数后面加i,相当于变成:
(y*10^k+i)%m
=(y%m * 10^k%m + i)%m
那么转移方程就出来了
k: i有多少位
f(i) = ((f(i-1) * 10^k) %m + i) %m
然后我们算出矩阵
已知量肯定是f(i-1),i,由于有个10^k,我们还要补个1才能计算
[f(i-1), i, 1] * [A] = [f(i), i+1, 1]
[A]:
[10^k, 0, 0]
[1, 1, 0]
[0, 1, 1]
答案不就是[f(i-1), i, 1]*[A]^(i-1)吗
但是k会变化!
所以考虑分段处理,分19段
k的长度:对应的值
1 :0~9
2 :10~99
3 :100~999
4 :1000~9999
............
18 : 10^17~10^18-1
19 : 10^18
那么我们分别求出k的长度以及其对应的值,再改变一下矩阵,不就可以套算答案的那个公式了吗?
具体地,我们要对n进行分段,分成k段,每段长度是i
为了方便,我们k从1开始(也可以从0开始,但是改变矩阵的时改变它的那个值要+1)
k的长度枚举范围应该是1~n的长度
那么就要考虑如何算出k所对应的这个区间里面的数有多少个了。例如k=3,对应的区间的数个数就是 999-100+1=900
我们可以算出k对应的下限值。例如k=2,它对应的下限值是10,也就是 10^(k-1),上限值是99,也是可以确定的,也就是10^k-1,那不就可以算了吗
但是有个细节,我们得分类讨论
1.现在循环的时候,n远远大于k所对应的区间的最大值,也就是n>k对应的区间的上限值,我们就可以直接算。例如n=1002,k=3,那么k对应的值的范围是100~999,我们可以直接用k对应的区间的上限减去下限(999-100+1)
那么一般情况:n>10^k,上限值-下限值,也就是 10^k-1-10^(k-1)+1,整理下成为 10^k-10^(k-1)
2.现在循环的时候,n就在k所对应的区间之间,也就是n<k对应的区间的上限值,不就把上限值给调整下,变成n吗。
n-10^(k-1) +1
#include <bits/stdc++.h> #define int unsigned long long using namespace std; const int N=105; int n, m, cnt=0, tmp=0, p[N]; struct mp { int n, m; int a[N][N]; void init(int row, int col, bool isI) { n=row, m=col; memset(a, 0, sizeof a); if (isI) for (int i=1; i<=row; i++) a[i][i]=1; } }A, B; mp operator * (const mp A, const mp B) { mp C; C.init(A.n, B.m, 0); for (int i=1; i<=A.n; i++) for (int j=1; j<=B.m; j++) for (int k=1; k<=A.m; k++) C.a[i][j]=(C.a[i][j]+A.a[i][k]*B.a[k][j])%m; return C; } mp qpow_mp(mp A, int k) { mp res; res.init(A.n, A.m, 1); while (k>0) { if (k&1) res=res*A; A=A*A; k>>=1; } return res; } signed main() { p[0]=1; for (int i=1; i<=19; i++) p[i]=p[i-1]*10; scanf("%lld%lld", &n, &m); A.init(1, 3, 0); A.a[1][1]=0, A.a[1][2]=1, A.a[1][3]=1; B.init(3, 3, 0); B.a[1][2]=0, B.a[1][3]=0; B.a[2][1]=1, B.a[2][2]=1, B.a[2][3]=0; B.a[3][1]=0, B.a[3][2]=1, B.a[3][3]=1; tmp=n; while (tmp>0) tmp/=10, cnt++; for (int i=1; i<=cnt; i++) { int sum=0; B.a[1][1]=p[i]%m; if (p[i]>n) sum=n-p[i]/10+1; else sum=(p[i]-1)-p[i]/10+1; A=A*qpow_mp(B, sum); } printf("%lld ", A.a[1][1]); return 0; }