洛谷3216 HNOI2011 数学作业(矩乘优化递推)

题目链接

首先我们考虑,正常的 O ( n ) O(n) O(n)复杂度的计算应该如何计算

我们令 f [ i ] f[i] f[i]表示用 1 − i 1-i 1i这些数能拼出来的数是多少

那么 f [ i ] = ( f [ i − 1 ] ∗ 1 0 i 的 位 数 + i ) m o d    m f[i]=(f[i-1]*10^{i的位数}+i) \mod m f[i]=(f[i1]10i+i)modm

QWQ我们考虑怎么优化这个过程。

由于不同的位数计算的时候差异很大,而且我们并没有一个好的方法将位数不同的数放到一起计算。

那我们考虑单独计算每一位数的贡献。

由于是从 f [ i − 1 ] f[i-1] f[i1]转移到 f [ i ] f[i] f[i]很容易想到矩阵乘法

考虑到转移的时候还涉及到 i i i的因素
那我们发生的两个矩阵分别是

{f[i-1],i-1,1} -> {f[i],i,1}

然后中间的转移矩阵是

10^x  0 0
1     1 0
1     1 1

qwq感觉细节还是很多的。

qwq就是边界的问题 嗯

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 10;
int mod;
int n;
int power[101010];
struct Ju{
	int x,y;
	int a[maxn][maxn];
	Ju operator *(Ju b)
	{
		Ju ans;
		ans.x=x;
		ans.y=b.y;
		memset(ans.a,0,sizeof(ans.a));
		for (int i=1;i<=ans.x;i++)
		  for (int j=1;j<=ans.y;j++)
		    for (int k=1;k<=y;k++)
			  ans.a[i][j]=(ans.a[i][j]+a[i][k]%mod*b.a[k][j]%mod)%mod;
		return ans; 
	}
};
Ju ans;
Ju qsm(Ju i,int j)
{
  ans.x=i.x;
  ans.y=i.y;
  memset(ans.a,0,sizeof(ans.a));
  for (int p=1;p<=i.x;p++) ans.a[p][p]=1;
  while (j)
  {
  	if (j&1) ans=ans*i;
  	i=i*i;
  	j>>=1;
  }
  return ans;
} 
Ju a;
Ju b;
void solve(int pre,int now,int lim)
{
	memset(a.a,0,sizeof(a.a));
	memset(b.a,0,sizeof(b.a));
	a.x=1;a.y=3;
	a.a[1][1]=pre%mod;
	a.a[1][2]=(power[now]-1+mod)%mod;
	a.a[1][3]=1;
	b.x=3;
	b.y=3;
	b.a[1][1]=power[now+1]%mod;
	b.a[2][1]=1;
	b.a[2][2]=1;
	b.a[3][1]=1;
	b.a[3][2]=1;
	b.a[3][3]=1;
	Ju tmp=qsm(b,lim-power[now]+1);
	a=a*tmp; 
}
signed main()
{
  n=read(),mod=read();
  power[0]=1;
  for (int i=1;i<=19;i++) power[i]=power[i-1]*10;
  int pre=0;
  for (int i=0;i<=18;i++)
  {
  	 if (power[i]>n) break;
  	 solve(pre,i,min(power[i+1]-1,n));
  	 pre=a.a[1][1];
  }
  cout<<pre;
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值