前言:
在之前已經學習過矩陣快速冪的用法,那些只是基礎。
在ICPC中大多數難度較高,且並不是簡單的只需要常數的矩陣或者簡單的圖上問題,而是結合dp方程去推匯出來轉移矩陣。
trick:
例題:
連結:https://ac.nowcoder.com/acm/contest/88880/E
來源:牛客網
給出兩個整數 \(n,k\),有一個正整數集合 \(X=\{1,2,3,\cdots,n-1,n\}\)。你需要求出滿足以下條件的對映 \(f: X \to X\) 的個數模 \(998244353\) 的值:
-
\(f \circ f=I_X\),即 \(f(f(x))=x\)
-
\(|f(x)-x| \le k\)
\(1 \le T \le 500,1 \le n \le 10^{18},0 \le k \le 7\)
題解:
首先我們化簡一下題意,大致意思就是說,我們每一個點可以選擇自己,或者選擇自己相鄰距離在k以內的點,這兩個點就會連線在一起,兩個點連線了之後就不能再選了。問把所有點連線有多少種方案。
由於這個n的範圍實在是太大了,我們可以考慮矩陣快速冪,但這個也是後話。我們可以先考慮設計一個狀態,假設k固定的情況下,則我們對於每一個點 \(i\) 都去考慮和它前面的點相連線。那麼考慮前面多少個點好呢?我們只需要考慮前面k個點的情況就好了,而k的範圍<=7,完全可以用狀壓。
用一個s1表示前面k個點的情況。每一位用0表示已經連線了,1表示還沒有連線。
設s2是我們操作之後的狀態,我們首先分析出來,可以列舉前面s1的每一位,如果這一位是1的話,就可以把這一個1消掉,s2就是消去那一位1的s1再左移1位,因為我們用0表示已經連線,所以s2雖然比s1多了一位,但是那一位是0,所以無傷大雅,本質上還是k位。
同時再討論一些情況,比如我們還可以s2還可以是s1左移1位,s1左移1位再異或1,有一種特殊情況是當s1>>(k-1)&1==1時,我們就只能連線這一位,不能有別的操作,這樣才能保證在前k位之前的所有的點都是被連線的。
大致的思路就是這樣。
上述的思路都是在s1左移1位(處理出來1位)的情況下得出來的,把這個矩陣稱為g[0],下標是2的0次方;ps:剛才討論s1->s2,s1便是矩陣的橫座標,s2便是矩陣的縱座標。
所以我們設一個初始的1(1<<k)的矩陣,去乘以n次這個g0矩陣就可以得到答案了。(此處如果不明白的話可以再複習一下矩陣快速冪)。
但是n是1e18,所以此處我們可以使用矩陣快速冪,g[i]=g[i-1]*g[i-1];
然後n拆成2進位制的形式去處理就好了。
以下貼上程式碼:
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }
inline int max(int a, int b) { if (a < b) return b; return a; }
inline int min(int a, int b) { if (a < b) return a; return b; }
typedef pair<int, int> PII;
//=======================================================================
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 998244353;
const int INF = 1e16;
int n, m, T;
Mat f[8][64];
//=======================================================================
signed main() {
kuaidu();
for(int k=0;k<=7;k++){
f[k][0].init(1<<k,1<<(k),0,0);
rep(s,0,(1<<k)-1){
if(s>>(k-1)&1){
f[k][0][s][(s<<1)^(1<<k)]=1;
}
else {
f[k][0][s][(s<<1)|1]=1;
f[k][0][s][s<<1]=1;
rep(i,0,k-1-1){
if(s>>i&1){
f[k][0][s][(s^(1<<i))<<1]=1;
}
}
}
}
rep(i,1,63) {
f[k][i]=f[k][i-1]*f[k][i-1];
}
}
cin>>T;
while(T--){
int k;
cin>>n>>k;
Mat ans;
ans.init(0,1<<k,0,0);
ans[0][0]=1;//初始化設定
rep(i,0,63){
if(n>>i&1) ans=ans*f[k][i];
}
cout<<ans[0][0]<<endl;
}
return 0;
}
矩陣板子:
struct Mat {
int a[140][260];
int n, m, st;
Mat(){}
Mat(int n_, int m_, int fl = 0, int st_ = 1) {
n = n_, m = m_, st = st_;
if (!fl) rep(i, st, n) rep(j, st, m) a[i][j] = 0;
if (fl == INF) rep(i, st, n) rep(j, st, m) a[i][j] = INF;
if (fl == 1) rep(i, st, n) rep(j, st, m) a[i][j] = (i == j);
}
void init(int n_, int m_, int fl = 0, int st_ = 1){
n = n_, m = m_, st = st_;
if (!fl) rep(i, st, n) rep(j, st, m) a[i][j] = 0;
if (fl == INF) rep(i, st, n) rep(j, st, m) a[i][j] = INF;
if (fl == 1) rep(i, st, n) rep(j, st, m) a[i][j] = (i == j);
}
int* operator [] (int x) { return a[x]; }
Mat operator * (Mat b) {
Mat res(n, b.m, 0, b.st);//記得賦值
rep(i, st, n) rep(j, st, b.m) rep(k, st, m) {
res[i][j] += a[i][k] * b[k][j] % mod;
res[i][j] %= mod;
}
return res;
}
Mat operator + (Mat b) {
Mat res(n, b.m, 0, b.st);//記得賦值
rep(i, st, n) rep(j, st, b.m) {
res[i][j] = a[i][j] + b[i][j];
res[i][j] %= mod;
}
return res;
}
static Mat kuai(Mat A, int b) {
Mat tem(A.n, A.m, 1, A.st);
while (b) { if (b % 2) tem = tem * A; b = b / 2, A = A * A; }
return tem;
}
//指數從0開始的求和
static Mat kuai_sum(Mat A, int b) {
Mat tem(A.n, A.m, 1, A.st); Mat sum = tem;
while (b) {
if (b % 2) tem = sum + tem * A;
sum = sum + sum * A; A = A * A, b /= 2;
}
return tem;
}
void out() {
rep(i, st, n) { rep(j, st, m) cout << a[i][j] << " "; cout << endl; }
}
};