洛谷·[HNOI2011]数学作业

初见安~这里是传送门:洛谷P3216 数学作业

洛谷·[HNOI2011]数学作业_第1张图片

题解

题意很简单,求n个数依次写过去得到的这个数模m的值。

我们很容易得到一个线性的递推式是:f_i=(f_{i-1}*10^w+i)mod\ m ,其中w是i的位数。但是题目的话O(n)明显过不了。

线性递推式+线性过不了,我们就可以想到矩阵快速幂了。

f的值需要计算,i的值需要累加,所以我们的矩阵里面需要放三个东西:f_i,i,1。转移就是:

\begin{bmatrix} f_i\ \\ i\\ 1 \end{bmatrix} = \begin{bmatrix} 10^w \ 1 \ 1\\ \ 0 \ \ \ 1 \ 1\\ \ 0 \ \ \ 0 \ 1 \end{bmatrix} * \begin{bmatrix} f_{i-1}\\ i-1\\ 1 \end{bmatrix}

但是转移矩阵中的10^w会因为数位的改变而改变 所以我们还得枚举n的数位改变转移矩阵来进行快速幂。

手枚可得:

n<10时,有9个数w=1;

n<100时,有90个数w=2;

n<1000时,有900个数w=3;

……

n<10^x时,有10^x-10^{x-1}个数w=x。

所以就可以写代码了。

当然,上文3*1的矩阵放到3*3的矩阵中会方便很多,可以直接在每一行后面补两位0。

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
ll read() {
	ll x = 0, f = 1, 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;
}


ll n, m;
struct node {
	ll x[3][3];
}tran, init, tmp;

node mul(node a, node b) {//矩阵乘法
	node c; memset(c.x, 0, sizeof c.x);
	for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) for(int k = 0; k < 3; k++) c.x[i][j] = (c.x[i][j] + a.x[i][k] * b.x[k][j] % m) % m;
	return c;
	
}

node pw(node a, ll b) {//快速幂
	node ans; memset(ans.x, 0, sizeof ans.x); ans.x[0][0] = ans.x[1][1] = ans.x[2][2] = 1;
	while(b) {if(b & 1) ans = mul(a, ans); a = mul(a, a), b >>= 1;}
	return ans;
}

signed main() {
	n = read(), m = read();
	tran.x[0][1] = tran.x[0][2] = tran.x[1][1] = tran.x[1][2] = tran.x[2][2] = 1;
	init.x[2][0] = 1;//tran是转移矩阵 先初始化;init是目标矩阵
	
	for(ll i = 1; i <= n; i *= 10) {
		memcpy(tmp.x, tran.x, sizeof tmp.x); tmp.x[0][0] = i * 10 % m;//tmp是当前转移矩阵
		node ttp = pw(tmp, min(i * 10 - i, n - i + 1));//次数注意上界
		init = mul(ttp, init);//init是目标矩阵
	}
	printf("%lld\n", init.x[0][0]);
	return 0;
}

迎评:)
——End——

你可能感兴趣的:(矩阵快速幂)