巨大的数(dp+矩阵加速)

第3题     巨大的数 查看测评数据信息

小明定义了一种生成大数的函数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;
}

  

 

 

posted @ 2024-08-16 22:08  cn是大帅哥886  阅读(25)  评论(0)    收藏  举报