题解 P3216 【[HNOI2011]数学作业】
简单的矩阵递推练手题
前言:听完了矩阵递推的网课后我打开了团队练习题单,发现自己并不会矩阵游戏中矩阵满足费马小定理的证明,于是只好来切这道题,但事实上,抛开细节对代码构筑能力的考验,这道题比矩阵游戏简单,甚至可以说在你明白这是道矩阵递推的题后,简直就是水题。\ 一开始先不想矩阵递推,不难推出公式:
long long ans=0;
for(long long i=1;i<=n;i++)
{
//ws[i]是i的位数
ans=ans*ws[i]+i;//不模的简洁版
ans=(ans*ws[i]%mod+i)%mod;//取模的细节版
}
明显会爆longlong,
又比如位数为3时:
举了两个例子后思路就清晰了,我们可以先求1到10,再求10到100,然后是100到1000等等。
struct node
{
int x,y;//行列数
int a[5][5];
void empty()//清空哦
{
x=y=0;
for(int i=0;i<=4;i++)
for(int j=0;j<=4;j++)
{
a[i][j]=0;
}
}
void check()//验错用的
{
for(int i=1;i<=x;i++)
{
for(int j=1;j<=y;j++)
printf("%lld ",a[i][j]);
printf("\n");
}
}
}q[20],p,p2;//q表示对应位数下使用的3X1初始矩阵,p是用来维护递推关系的柿子,p是单位矩阵0.
/*
q[1]:
0
1
1
q[2]:
123456789
10
1
*/
void pre()//初始化
{
int temp=1;
for(int i=1;i<=18;i++)
{
q[i].x=3,q[i].y=1;
q[i].a[2][1]=temp;
temp=temp*10;
q[i].a[3][1]=1;
}
p.x=p.y=p2.x=p2.y=3;
p.a[1][1]=10;
p.a[2][2]=p.a[1][2]=p.a[2][3]=p.a[3][3]=1;
p2.a[1][1]=p2.a[2][2]=p2.a[3][3]=1;
}
void work(int now,int pre,int to)//现在的位数,上一次计算到的位置,这一次该计算到的位置
{
//q[now]是递推出来的3X1矩阵,p是左边用于递推的3X3矩阵
int temp=q[now-1].a[1][1];//继承
q[now].a[1][1]=temp;
if(now==ws_ans)//如果当前位数和答案一致,可以合法的到达答案了
{
node b=quickpow(p,n-pre);//矩阵快速幂
q[now]=b*q[now];//计算出最终的矩阵
printf("%lld",q[now].a[1][1]);
return;
}
else q[now]=quickpow(p,to-pre)*q[now];//直接求解下一个位数的第一个矩阵
p.a[1][1]=p.a[1][1]*10%mod;//此处记得取模
work(now+1,to,(to+1)*10-1);//递归下一位数
}
然后还要注意取模的问题,不要爆longlong,基本上自己手测几个样例之后就能过了。\ 送一组大样例,输入19191145141919 415411 ,输出246506。\ 原代码如下:(有些变量名与上面不一样,注意区分)
#include<cstdio>
#define int unsigned long long
using namespace std;
int n,m,ws;
struct node
{
int x,y;
int a[5][5];
void empty()
{
x=y=0;
for(int i=0;i<=4;i++)
for(int j=0;j<=4;j++)
{
a[i][j]=0;
}
}
void check()
{
for(int i=1;i<=x;i++)
{
for(int j=1;j<=y;j++)
printf("%lld ",a[i][j]);
printf("\n");
}
}
}q[20],p,p2;
void pre()
{
int temp=1;
for(int i=1;i<=18;i++)
{
q[i].x=3,q[i].y=1;
q[i].a[2][1]=temp;
temp=temp*10;
q[i].a[3][1]=1;
}
p.x=p.y=p2.x=p2.y=3;
p.a[1][1]=10;
p.a[2][2]=p.a[1][2]=p.a[2][3]=p.a[3][3]=1;
p2.a[1][1]=p2.a[2][2]=p2.a[3][3]=1;
}
node operator*(node a,node b)
{
node o;
o.empty();
int x=a.x;
int y=a.y;
int z=b.y;
for(int i=1;i<=x;i++)
for(int j=1;j<=y;j++)
for(int k=1;k<=z;k++)
o.a[i][k]=(o.a[i][k]+(a.a[i][j]%m)*(b.a[j][k]%m))%m;
o.x=x;
o.y=z;
return o;
}
node quickpow(node a,int b)
{
node ans=p2,temp=a;
while(b)
{
if(b&1)ans=ans*temp;
temp=temp*temp;
b/=2;
}
return ans;
}
void work(int now,int pre,int to)
{
int vra=q[now-1].a[1][1];
q[now].a[1][1]=vra;
if(now==ws)
{
node b=quickpow(p,n-pre);
q[now]=b*q[now];
printf("%lld",q[now].a[1][1]);
return;
}
else q[now]=quickpow(p,to-pre)*q[now];
p.a[1][1]=p.a[1][1]*10%m;
work(now+1,to,(to+1)*10-1);
}
signed main()
{
scanf("%lld%lld",&n,&m);
pre();
int temp=n;
while(temp)
{
temp/=10;
ws++;
}
work(1,0,9);
}
撰写不易,求通过。