[HNOI2011]数学作业

洛咕

题意:给定正整数\(N\)\(MOD\),计算\(Concatenate(1.. N)\mod MOD\) 的值,其中 $ Concatenate (1 .. N) $ 是将所有正整数\(1,2,…,N\)顺序连接起来得到的数.例如,\(N = 13\) , \(Concatenate (1 .. N)=12345678910111213\) .

分析:令\(s_i\)为前i个数得到的数,\(len_i\)表示i的位数,则不难写出递推式\(s_i=s_{i-1}*10^{len_i}+i\).发现如果\(len_i\)是固定的话,就是矩阵加速递推的基本做法了.再想一想,\(len_i<=18\),即最多只有18种可能,于是我们可以针对每一种可能都做一遍矩阵加速递推,只要考虑如何合并就好了.

先考虑转化为矩阵,假设我们已知\(S(i-1)=[f(i-1),i,1]\),要得到\(S(i)=[f(i),i+1,1]\),则可以得到转移矩阵\(T=[\begin{matrix} 10^{len_i} & 0 & 0 \\1 & 1 & 0\\0 & 1 & 1 \end{matrix}]\).

然后本题主要的难点在于如何合并?我们每次处理的是19,1099,100~999,我下面的代码中设置了一个for循环,让s从10开始每次乘10,还有一个now变量记录当前(上次)处理的是哪一段.所以我们当前要处理的段就是(s-1)-now.

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s*w;
}
LL n,mod,now;
struct matrix{
    LL a[3][3];
    matrix operator *(matrix b){//重载运算符为矩阵乘法
		matrix c;
		for(int i=0;i<3;i++)
	    	for(int j=0;j<3;j++)
				c.a[i][j]=0;
		for(int i=0;i<3;i++)
	    	for(int j=0;j<3;j++)
				for(int k=0;k<3;k++)
		    		c.a[i][j]=(c.a[i][j]+1LL*a[i][k]*b.a[k][j])%mod;
		return c;
    }
}S,T;
int main(){
    n=read();mod=read();
    S.a[0][1]=S.a[0][2]=1;//S(0)=[0,1,1]
    for(LL s=10;now<n;s*=10){
		T.a[1][0]=T.a[1][1]=T.a[2][1]=T.a[2][2]=1;
		T.a[0][1]=T.a[0][2]=T.a[1][2]=T.a[2][0]=0;
		T.a[0][0]=s%mod;//10^len
		LL b=min(s-1,n)-now;
//当前处理的段,这里s-1和n取min是要考虑到实际情况
		while(b){//直接矩阵快速幂
	    	if(b&1)S=S*T;
	    	T=T*T;
	    	b>>=1;
		}
		now=min(s-1,n);//now记录当前处理的段
    }
    printf("%lld\n",S.a[0][0]);
    return 0;
}

posted on 2019-04-02 20:00  PPXppx  阅读(134)  评论(0编辑  收藏  举报