雜項——矩陣加速(進階)

AdviseDY發表於2024-09-17

前言:

在之前已經學習過矩陣快速冪的用法,那些只是基礎。

在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; }
	}
};

相關文章