bzoj2326: [HNOI2011]数学作业

本文解析了HNOI2011的一道数学作业题,通过构造矩阵并使用矩阵快速幂的方法求解Concatenate(1..N)ModM问题。详细介绍了算法思路与实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2326: [HNOI2011]数学作业

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 2552   Solved: 1507
[ Submit][ Status][ Discuss]

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≤10^18且1≤M≤10^9.

Output

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

Sample Input

13 13

Sample Output

4

HINT

Source


题解:好题!

点击打开链接

想想看,如果n范围比较小时你会怎么做?
我们应该想到,比如说对于序列1234567,要取模的数是13,那么我们会先让1对13取模得到1,再让12对13取模得到12,再让123对13取模得到6,再让64对13取模得到12,再让125对13取模得到8,再让86对13取模得到8,最后让87对13取模得到9。这与1234567直接对13取模的值是相同的。其中的数学证明不再赘述。
于是我们可以得到递推式,对于序列f(n),f(n)=f(n-1)×10^len(n)+n(其中len(n)表示n这个数字有几位)。
由于n的范围极大,我们考虑用矩阵快速幂转移优化,构造矩阵如下:
f(n) 10^len(n) 1 1 f(n-1)
n = 0 1 1 × n-1
1 0 0 1 1
我们转移时,根据1~9时len(i)相同,10~99时len(i)相同,100~999时len(i)相同划分为不同的区块进行转移优化。



代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3;
struct node{
    long long a[N][N];
    void clr(){memset(a,0,sizeof(a));}
}ans,p[20];
long long n,mod,now,t;
node operator * (node x,node y)
{
    node z; z.clr();
    for (int i=0;i<3;++i)
    for (int j=0;j<3;++j)
    for (int k=0;k<3;++k)
    z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j])%mod;
    return z;
}
node Pow(node x,long long y)
{
    node tmp=x;
    for (;y;y>>=1,x=x*x) if (y&1) tmp=tmp*x;
    return tmp;
}
long long calc(long long x,long long y)
{
    long long tmp=1;
    for (;y;y>>=1,x=x*x) if (y&1) tmp=tmp*x;
    return tmp;
}
int main()
{
    scanf("%lld%lld",&n,&mod);
    ans.clr(); ans.a[2][0]=1;
    p[0].clr(); p[0].a[0][0]=p[0].a[0][1]=p[0].a[0][2]=p[0].a[1][1]=p[0].a[1][2]=p[0].a[2][2]=1;
    for (int i=1;i<=19;++i) p[i]=p[i-1],p[i].a[0][0]*=10,p[i].a[0][0]%=mod;
    for (long long i=0,j=9,k=0;k<min(n,(long long)999999999999999999);k+=9*calc(10,i),++i)
    ans=Pow(p[i+1],min(n-k,9*calc(10,i))-1)*ans;
    if (n==1000000000000000000) ans=p[19]*ans;
    printf("%lld\n",ans.a[0][0]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值