BZOJ2326 || 洛谷P3216 [HNOI2011]数学作业【矩阵优化DP】

Time Limit: 10 Sec
Memory Limit: 128 MB

Description

小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题:给定正整数 N 和 M
要求计算 Concatenate (1 … N) Mod M 的值,其中 Concatenate (1 …N)是将所有正整数 1, 2, …, N 顺序连接起来得到的数。
例如,N = 13, Concatenate (1 … N)=12345678910111213.小C 想了大半天终于意识到这是一道不可能手算出来的题目,
于是他只好向你求助,希望你能编写一个程序帮他解决这个问题。

Input

只有一行且为用空格隔开的两个正整数N和M,
1 ≤ N ≤ 1 0 1 8 1≤N≤10^18 1N1018 1 ≤ M ≤ 1 0 9 1≤M≤10^9 1M109

Output

仅包含一个非负整数,表示 Concatenate (1 … N) Mod M 的值。


题目分析

看N的范围显然是需要一个log级别的算法,所以想到矩阵快速幂

d p [ n ] dp[n] dp[n]表示拼接1~N后膜M的值
那么有 d p [ n ] = d p [ n − 1 ] ∗ 1 0 l e n ( n ) + n dp[n]=dp[n-1]*10^{len(n)}+n dp[n]=dp[n1]10len(n)+n (len(n)表示n的位数)
写成矩阵形式
[ d p [ n − 1 ] n − 1 1 ] × [ 1 0 l e n ( n ) 0 0 1 1 0 1 1 1 ] = [ d p [ n ] n 1 ] \begin{bmatrix} dp[n-1] \quad n-1 \quad 1 \end{bmatrix}\times\begin{bmatrix} 10^{len(n)} & 0 & 0 \\ 1 & 1 & 0 \\ 1 &1 & 1 \end{bmatrix} =\begin{bmatrix} dp[n] \quad n \quad 1 \end{bmatrix} [dp[n1]n11]×10len(n)11011001=[dp[n]n1]

初始 d p [ 1 ] = 1 m o d    m dp[1]=1\mod m dp[1]=1modm
快速幂的时候 1 0 l e n ( n ) 10^{len(n)} 10len(n)这样的变量是不能存在的
但注意到n最多只有18位
所以只要将位数分开枚举就好


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
 
lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}
 
lt n,p,len,tn;
 
struct matrix
{
	lt mat[20][20],row,col;
	matrix(int r=0,int c=0){ 
		row=r; col=c; 
		for(int i=1;i<=row;++i)
		for(int j=1;j<=col;++j)
		mat[i][j]=0;
	}
};

matrix operator *(matrix a,matrix b){ 
	matrix c=matrix(a.row,b.col);
	for(int i=1;i<=a.row;++i)
	for(int j=1;j<=b.col;++j)
	for(int k=1;k<=a.col;++k)
	{
		c.mat[i][j]+=(a.mat[i][k]*b.mat[k][j])%p;
		c.mat[i][j]%=p;
	}
	return c;
}

matrix qpow_mat(matrix a,lt k)
{
	matrix res=matrix(a.row,a.col);
	for(int i=1;i<=a.row;++i) res.mat[i][i]=1;
	while(k){
		if(k&1) res=res*a;
		a=a*a; k>>=1;
	}
	return res;
}
 
lt qpow(lt a,lt k)
{
    lt ans=1;
    while(k>0){
        if(k&1) ans*=a;
        a*=a; k>>=1;
    }
    return ans;
}
 
int main()
{
    n=tn=read();p=read();
    
    matrix h=matrix(3,3);
    h.mat[1][1]=1; h.mat[1][2]=0; h.mat[1][3]=0;
    h.mat[2][1]=1; h.mat[2][2]=1; h.mat[2][3]=0;
    h.mat[3][1]=1; h.mat[3][2]=1; h.mat[3][3]=1;
     
    while(tn>0){ len++; tn/=10;}
     
    matrix rem=matrix(3,3),tt;
    for(int i=1;i<=3;++i) rem.mat[i][i]=1;
    for(int i=1;i<len;++i)
    {
        h.mat[1][1]=qpow(10,i)%p;
        tt=qpow_mat(h,qpow(10,i)-qpow(10,i-1)-((i==1)?1:0));
        rem=rem*tt;
    }
     
    h.mat[1][1]=qpow(10,len)%p;
    tt=qpow_mat(h,n-qpow(10,len-1)+1-((len==1)?1:0));
    rem=rem*tt;
     
    matrix f=matrix(1,3),res;
	f.mat[1][1]=f.mat[1][2]=f.mat[1][3]=1;
    res=f*rem;
     
    printf("%lld",res.mat[1][1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值