zoukankan      html  css  js  c++  java
  • 洛谷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;
    }
  • 相关阅读:
    Linux系统常见的压缩与打包
    java 语言规范 java language specifications
    java 枚举
    github邮箱验证技巧
    关于 python
    博客园 编程基础 精华
    fiddler
    一个牛人写的博客
    使用xmarks同步 chrome ie firefox safari书签
    linux 中的 tar 解压
  • 原文地址:https://www.cnblogs.com/yanshannan/p/7392294.html
Copyright © 2011-2022 走看看