洛谷P3216 [HNOI2011]数学作业

题目描述

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

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

输入输出格式

输入格式:
从文件input.txt中读入数据,输入文件只有一行且为用空格隔开的两个正整数N和M,其中30%的数据满足1≤N≤1000000;100%的数据满足1≤N≤1018且1≤M≤109.

输出格式:
输出文件 output.txt 仅包含一个非负整数,表示 Concatenate (1 .. N) Mod M 的值。

输入输出样例

输入样例#1:
13 13
输出样例#1:
4

首先写出递推关系式:令s[i]为前i个数连接得到的数,c(i)表示i的位数,有s[i]= 10^c(i)+s[i-1]+i;

c(i)随i变化,无法在转移矩阵T中表示。如果c(i)固定,就可以套用矩阵快速幂的方法进行优化了!

考虑位数随着i的变化最多只会变化18次,因此可以按位数分段进行矩阵快速幂。

枚举位数,那么有:
{10^k,0,0
{f[i-1],i-1,1}* 1, 1 ,0 = {f[i],i,1}
1, 1 ,1}
这里在状态矩阵中加了一个永远为1的值,用于辅助i每次加一。

将多位数按每次加数时前一个数所乘的10的个数分为10-99,100-999。。。的数字段,方便进行矩阵乘法 。

实现代码时套模拟矩阵乘法模板和快速幂模板即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
ll n,m,S[3][3]={},T[3][3]={}; 
void mmul(ll a[3][3],ll b[3][3],ll s[3][3])//模拟矩阵乘法 
{
    ll tmp[3][3]={};//此处a和s是同一数组,必须开一个数组作中转站(而不是像在重载运算符中结果数组是独立的,不受乘数影响) 
    for(int i=0;i<3;i++)
       for(int j=0;j<3;j++)
          for(int k=0;k<3;k++)
             tmp[i][j]=(tmp[i][j]+(a[i][k]%m)*(b[k][j]%m))%m;//两个长整型数相乘,一定要分别取余,否则会爆负数(超范围了) 
    for(int i=0;i<3;i++)
       for(int j=0;j<3;j++)
          s[i][j]=tmp[i][j];//在不影响乘数的前提下得出结果 
}
void cal(ll t,ll last)
{
    memset(T,0,sizeof(T));
    T[0][0]=t;//每次加数时前一个数应进的位数 
    T[1][0]=T[1][1]=T[2][0]=T[2][1]=T[2][2]=1;
    ll y=last-t/10+1;//y决定在该数字段下加数的次数 
    while(y)//快速幂模板 
    {
        if(y&1)mmul(S,T,S);//模拟矩阵乘法 
        mmul(T,T,T);
        y>>=1;
    }
}
int main()
{
    scanf("%lld%lld",&n,&m);
    S[0][0]=S[1][1]=S[2][2]=1;//初始化
    ll t=10;
    while(n>=t)
    {
        cal(t,t-1);//将多位数按每次加数时前一个数所乘的10的个数分为10-99,100-999。。。的数字段,方便进行矩阵乘法 
        t*=10;
    }
    cal(t,n);//如果n<10,就说明每次加数时只要进一位即可 
    printf("%lld",S[2][0]);
    return 0;
}
原文地址:https://www.cnblogs.com/yanshannan/p/7392294.html