洛谷·[HNOI2011]數學作業

初見安~這裏是傳送門:洛谷P3216 數學作業

題解

題意很簡單,求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<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
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——

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章