###### tags: `小菜雞解題日記`
# 主題三 : 動態規劃
## 內容
---
## LCS問題
### [裸LCIS](https://codeforces.com/contest/10/problem/D)
**解題心得**:這一題的難度竟然是2800真是嚇人,但其實它就是裸LCIS而已。
**解題想法**:LCIS的狀態就是把LIS和LCS的狀態合併在一起,所以狀態就是,$dp[i][j]$ 代表 $a$ 陣列前 $i$ 個和 $b$ 陣列前 $j$ 個,且 $b[j]$ 必選的情況下所能組成的LCIS長度。 我寫完後突然發現,LCS可以滾動成一維,而且用法不變ㄟ🤣🤣🤣。 我好弱現在才發現。
:::success
AC Code
:::spoiler
```cpp=
#include<bits/stdc++.h>
#define x first
#define y second
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define mem(x,val) memset(x,val,sizeof x)
#define pii pair<int,int>
#define pb emplace_back
#define ar array
#define int long long
#define FOR(i, a, n) for(int i = a; i <= n; i++)
#define F0R(i, n) FOR(i, 0, n-1)
#define wopen(x) freopen((x),"w",stdout)
#define ropen(x) freopen((x),"r",stdin)
using namespace std;
const int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
int n, m, st, ans, mx, cnt, a[505], b[505], dp[505][505], go[505];
void dfs(int u){
if(u == 0) return;
dfs(go[u]);
cout << b[u] << ' ';
}
signed main(void){
ios_base::sync_with_stdio(0); cin.tie(0);
cin >> n;
FOR(i, 1, n) cin >> a[i];
cin >> m;
FOR(i, 1, m) cin >> b[i];
FOR(i, 1, n){
int t = 0;
FOR(j, 1, m){
dp[i][j] = dp[i-1][j];
if(a[i] == b[j]){
dp[i][j] = dp[i-1][t] + 1;
go[j] = t;
}
if(a[i] > b[j] && dp[i-1][j] > dp[i-1][t])
t = j;
if(i == n && dp[i][j] > ans){
ans = dp[i][j];
st = j;
}
}
}
cout << ans << '\n';
dfs(st);
}
```
:::
---
## 背包問題
## bitset加速01背包
### [CF 755F](https://codeforces.com/contest/755/problem/F)
**解題心得** : 這大概是我目前自己解過最難度tag最高的題目了,雖然最後還是偷看了一下要怎麼用bitset加速。
**解題想法** : 這種難度比較高的題目通常都會用到3, 4個想法(雖然我根本沒解過幾題)。看到這種給出1 ~ n排列的題目,第一個想法就是把它縮成很多個環。縮完之後就可以用貪心判斷最大,最小也只會有兩種狀況,不是k就是k+1。如果我們可以選一些環使得長度和剛好為k,那答案就會是k,如果不行就會是k+1,因此背包的狀態只需要是0和1,我們就可以用bitset優化了!!順帶一題,因為這題是多重背包,所以要先使用二進制拆分。
::: success
AC Code
::: spoiler
```cpp=
#include<bits/stdc++.h>
#define x first
#define y second
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define mem(x,val) memset(x,val,sizeof x)
#define pii pair<int,int>
#define pb emplace_back
#define ar array
#define int long long
#define wopen(x) freopen((x),"w",stdout)
#define ropen(x) freopen((x),"r",stdin)
using namespace std;
const int N = 1e6 + 5;
int n, k, a[N], cnt, mn, mx, mxt, tot, len[N], num[N];
bitset<N> vis, dp;
signed main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
cin >> n >> k;
mxt = k;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
if(!vis[i]){
int t = 1;
vis[i] = 1;
for(int j = a[i]; j != i; j = a[j])
t++, vis[j] = 1;
len[t]++;
if(t >= 2 && mxt > 0){
int tt = min(mxt, t/2);
mxt -= tt;
mx += tt * 2;
}
}
}
if(mxt > 0){
mx += mxt * 1;
}
mx = min(mx, n);
dp[0] = 1;
for(int i = 2; i <= n; i++){
if(!len[i]) continue;
int j;
for(j = 0; (1ll<<j) <= len[i]; j++){
num[tot++] = (i<<j);
len[i] -= (1<<j);
}
if(len[i]) num[tot++] = i * len[i];
}
for(int i = 0; i < tot; i++)
dp = dp | (dp << num[i]);
// 傳說中的bitset優化
if(dp[k]) mn = k;
else mn = k + 1;
cout << mn << ' ' << mx << '\n';
}
/*
6 3
2 3 1 5 6 4
*/
```
:::
### [NTUOJ 1327](http://acm.csie.org/ntujudge/problem.php?id=1327)
**解題心得** : 我在解上一題時還沒領悟到bitset背包的真諦,直到解這一題才完全了解。
**解題想法** : 一開始先每個科目都選最低分,然後之後再考慮要怎麼加分,加分的時候bitset就派上用場了,每個科目可以加 $1\ \sim\ r[i] - l[i]$分,再乘上 $w[i]$,但是為了確保同一科目不會被重複加到,我們可以開兩個bitset,先把這個科目加分的結果更新到一個暫存的bitset,最後再和原本的bitset合併(因為可以選擇不加分)。
:::success
AC Code
:::spoiler
```cpp=
#include<bits/stdc++.h>
#define x first
#define y second
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define mem(x,val) memset(x,val,sizeof x)
#define pii pair<int,int>
#define pb emplace_back
#define ar array
#define int long long
#define wopen(x) freopen((x),"w",stdout)
#define ropen(x) freopen((x),"r",stdin)
#define FOR(i, a, b) for(int i = a; i < b; i++)
#define F0R(i, n) FOR(i, 0, n)
#define FOR1(i, n) FOR(i, 1, n+1)
using namespace std;
int T, b, n, totw, mn, cnt, mx, g, l[50], r[50], w[50];
bitset<250005> dp, tt;
signed main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
cin >> T;
while(T--){
cin >> b >> n;
mn = 0; mx = 0; totw = 0; dp = 0;
FOR(i, 0, n){
cin >> l[i] >> r[i] >> w[i];
mn += l[i] * w[i];
mx += r[i] * w[i];
totw += w[i];
}
if(mn >= b * totw) {
mn -= b * totw;
g = __gcd(mn, totw);
cout << mn/g << '/' << totw/g << '\n';
}
else {
dp[mn] = 1;
FOR(i, 0, n){
tt = 0;
FOR(j, 1, r[i] - l[i] + 1){
tt = tt | (dp<<((j) * w[i]));
}
dp = dp | tt;
}
int ans = 1e15;
FOR(i, 0, mx + 1){
if(dp[i] && abs(i - totw * b) < ans) ans = abs(i - totw * b);
}
if(ans == 0) cout << "0/1\n";
else {
g = __gcd(ans, totw);
cout << ans / g << '/' << totw / g << '\n';
}
}
}
}
```
:::
### [中市賽 雙層骨牌](http://www.tcgs.tc.edu.tw:1218/ShowProblem?problemid=d091)
**解題心得** : 這題很明顯會有多組解(題目也沒說),但我不知道要輸出哪一組,所以我先精神AC它。
---
## 環狀DP
### [ICPC 2020 F](https://codeforces.com/gym/102835/problem/F)
**解題心得** : 原本的想法是做很多次樹dp,最後再環狀dp。但後來聽老師說,遇到環狀dp,就先把環拆掉就好。然後就會變成簡單的樹dp了。
**解題想法** : 題目有確保只會有一個環,所以我們只要拆掉一條邊,就可以變成樹,然後我們再分開考慮被拆掉那條邊上的兩個點,是否要選,最後取最小值(結果我樹dp好像是用貪心解的)。
:::success
AC Code
:::spoiler
```cpp=
#include<bits/stdc++.h>
#define x first
#define y second
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define mem(x,val) memset(x,val,sizeof x)
#define pii pair<int,int>
#define pb emplace_back
#define ar array
#define int long long
#define wopen(x) freopen((x),"w",stdout)
#define ropen(x) freopen((x),"r",stdin)
#define FOR(i, a, b) for(int i = a; i < b; i++)
#define F0R(i, n) FOR(i, 0, n)
#define FOR1(i, n) FOR(i, 1, n+1)
using namespace std;
int ans, n, m, x, y, cnt;
vector<int> g[200005];
bitset<200005> vis, c;
void dfs(int u, int p, int pp){
for(int v : g[u]){
if(v == p) continue;
dfs(v, u, p);
}
if(p >= 0 && !vis[u] && !vis[p]){
if(c[p] == 0) c[p] = 1, cnt++;
vis[p] = vis[u] = 1;
}
return;
}
signed main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
FOR(i, 0, n + m){
int a, b;
cin >> a >> b;
if(x == 0 && y == 0 && a < n && b < n){
x = a;
y = b;
continue;
}
g[a].pb(b);
g[b].pb(a);
}
cnt = 1;
vis = c = 0;
vis[x] = c[x] = 1;
dfs(x, -1, -1);
vis = c = 0;
ans = cnt;
vis[y] = c[y] = 1;
cnt = 1;
dfs(y, -1, -1);
ans = min(cnt, ans);
cout << ans << '\n';
}
```
:::
---
## 樹DP
### 經典問題
**題目大意** :
樹上每個點有點權,求出樹上每條簡單路徑長度和。
路徑長度定義為路徑上所有點的點權和。
::: spoiler code
```cpp!
void dfs(int u, int p) {
sz[u] = (u <= n);
for (int v : tr[u]) {
if (v == p) continue;
dfs(v, u);
ans += w[u] * sz[u] * sz[v];
sz[u] += sz[v];
}
ans += w[u] * sz[u] * (tot - sz[u]);
}
```
:::
### [牛客競賽 K短直徑](https://ac.nowcoder.com/acm/contest/69/E)
**解題心得** : 一題看起來超級可怕的題目,而且數字都很大,我在比賽中看到可能沒有勇氣解。
**解題想法** : 這題用到了樹DP一個重要的概念,如果每次將兩子樹合併,就相當於在這兩個子樹的所有點上建一條邊,而且每兩個點都只會在以他們的$LCA$為根時被合併。
所以當我們合併完成後,就會得到一張完全圖,我們都知道完全圖的邊數是$\frac{N(N-1)}{2}$。所以複雜度會是$O(N^2)$。
這題給了一個$K$,但從上面的概念延伸一下不難發現,這樣複雜度會是$O(NK)$。
所以我們可以放心的DP,把每棵子樹有經過此節點的前$K$大路徑都存下來。然後每次合併就丟到PQ裡,維護PQ的大小為$K$。
ps: 在紀錄DP陣列的時候可以使用一些 $merge\ sort$
:::success
AC Code
:::spoiler
```cpp=
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#define sz(x) x.size()
#define pii pair<int, int>
#define pb emplace_back
#define F first
#define S second
#define ll long long
using namespace std;
const int N = 2e5 + 1;
int n, k, tot, h[N];
vector<ll> dp[N], tmp, ans;
priority_queue<ll, vector<ll>, greater<ll> > pq;
struct edge{
int v, w, next;
} e[(N - 1)* 2];
void add(int u, int v, int w){
e[tot] = {v, w, h[u]};
h[u] = tot++;
}
void dfs(int u, int p){
dp[u].pb(0LL);
//cout << "u = " << u << '\n';
for(int x = h[u]; x != -1; x = e[x].next){
int v = e[x].v, w = e[x].w;
if(v == p) continue;
dfs(v, u);
int nu = sz(dp[u]), nv = sz(dp[v]);
//cout << "nu = " << nu << " nv = " << nv << '\n';
for(int i = 0; i < nv; i++) dp[v][i] += w;
for(int i = 0; i < nu; i++){
for(int j = 0; j < nv; j++){
ll t = dp[u][i] + dp[v][j];
if(sz(pq) < k){
pq.push(t);
} else if(pq.top() < t){
pq.pop();
pq.push(t);
} else break;
}
}
tmp.clear();
int tot = min(k, nu + nv), i = 0, j = 0;
//dp[u].pb(-1LL);
//dp[v].pb(-1LL);
while(tot-- && (i < nu || j < nv)){
if((i < nu && dp[u][i] >= dp[v][j]) || j == nv) tmp.pb(dp[u][i++]);
else tmp.pb(dp[v][j++]);
}
dp[u] = tmp;
dp[v].clear();
}
return ;
}
int main(){
ios_base::sync_with_stdio(0); cin.tie(0);
cin >> n >> k;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i++){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dfs(0, -1);
while(!pq.empty()){
ans.pb(pq.top());
pq.pop();
}
while(!ans.empty()){
cout << ans.back() << '\n';
ans.pop_back();
}
}
```
:::
## 樹上背包
因為發現之前寫的都是 worst case $O(n^3)$ 的解,所以現在來記錄一下真的 $O(n ^ 2)$ 的解。
1. $j$ 最多枚舉到 $sz(v)$
2. $i - j$ 要小於原本的 $sz(u)$ 不然子樹會和自己重複合併到
::: spoiler code
```cpp=
void dfs(int u) {
sz[u] = 1;
for (int v : g[u]) {
dfs(v);
for (int i = sz[u] + sz[v]; i >= 1; i--) {
for (int j = max(1ll, i - sz[u]); j <= i and j <= sz[v]; j++) {
dp[u][i] = min(dp[u][i], dp[u][i - j] + dp[v][j]);
}
}
sz[u] += sz[v];
}
}
```
:::
---
## 排列DP
其實有很多種方式都能寫出帶有排列的DP,例如用狀態壓縮的$O(2^N)$DP,或是利用一些組合數學的DP,但還有一種很特別的DP,好像沒有正式的名稱,之前有看到有人稱他為insertion DP,但上網查卻完全查不到資料。
### [T. Permutaion](https://atcoder.jp/contests/dp/tasks/dp_t)
這是At Coder DP Contest裡的一題,所以大家其實應該都不陌生,但一開始寫這題的時候完全沒有想法,去看完題解之後以為自己會了,但仔細想了想之後,卻發現不知道為甚麼這樣會對。
今天終於弄懂了,所以決定來記錄一下這題想法。
#### **解法**
$dp[i][j]$ 代表 $1$ ~ $i$ 的排列滿足前 $i-1$ 個限制,且第 $i$ 個數字是 $j$ 的數量。
但是要怎麼從 $1$ ~ $i$ 的排列轉移到 $1$ ~ $i+1$ 的排列呢?
我們可以想像要插入一個數字,如果插入的數字是 $x(x \le i)$,那我們就把原本排列中大於等於$x$ 的數都加上 $1$,這樣我們就可以得到新的一組排列了。
而且這樣做也不會影響到兩數字之間的大小關係。
轉移式如下
$dp[i][j] =
\begin{cases}
s[i] = ">", \ \sum_{k = j}^{k \leq i - 1}{dp[i - 1][k]} \\
s[i] = "<", \ \sum_{k = 1}^{k < j}{dp[i - 1][k]}
\end{cases}$
要注意的地方是,當 $s[i] = ">"$ 時,$dp[i][j]$ 可以從 $dp[i-1][j]$ 轉移。
如果樸素轉移的話是 $O(N^3)$ ,但很明顯可以用前綴和做到 $O(N^2)$。
### [ABC 209 Deforestation](https://atcoder.jp/contests/abc209/tasks/abc209_f)
和上面很像的一題
---
## 數位DP
一開始不太喜歡數位DP,因為我只會抄模板,但後來學了用迴圈寫才發現我之前根本沒有弄懂數位DP,這其實是個蠻有趣的DP。
### [windy數](https://www.luogu.com.cn/problem/P2657)
~~數位DP就是在數位上做DP~~,所以通常設計的狀態都以 **目前是幾位數** 和 **最高位數字** 為核心。
像是這題的狀態就是 $dp[i][j]$ 表示以 $j$ 為首位數字的 $i$ 位數中有多少 windy 數。
接下來DP的轉移應該就很好想了,但重點其實不是DP,而是要怎麼弄出答案。
因為題目要詢問的是一個區間 $[a, b]$,但這樣其實不太好做,我們可以改成用求出 $[1, a-1]$ 和 $[1, b]$ 的答案再相減。
這樣就容易多了,但還是有些細節要注意。
我們可以把它分成 3 類。
假設我們現在要求的是範圍 $[1, x]$,且 $x$ 的最高位數字為 $L_x$
#### 1. 最高位為0
這邊的最高位就是 $x$ 的最高位。
如果最高位為0,剩下的要怎麼選都沒問題。
(或是可以想成位數 $<$ $x$ 的位數 的情況)
#### 2. 最高位 $<$ $L_x$
這樣除了最高位其他位都不會被限制。
#### 3. 最高位為 $L_x$
這是最麻煩的部分,現在會有兩種情況。
第一是次高位**等於** $x$ 的次高位,這樣再下一位也會被限制。
第二是次高位**小於** $x$ 的次高位,這樣接下來就不會被限制。
所以接下來每一位都要這樣討論。
::: spoiler code
```cpp=
#define ll loli
#include<bits/stdc++.h>
#define pb push_back
#define eb emplace_back
#define push emplace
#define lb(x, v) lower_bound(all(x), v)
#define ub(x, v) upper_bound(all(x), v)
#define sz(x) (int)(x.size())
#define re(x) reverse(all(x))
#define uni(x) x.resize(unique(all(x)) - x.begin())
#define all(x) x.begin(), x.end()
#define mem(x, v) memset(x, v, sizeof x);
#define int long long
#define pii pair<int, int>
#define inf 1000000000
#define INF 1000000000000000000
#define mod 1000000007
#define F first
#define S second
#define IO ios_base::sync_with_stdio(0); cin.tie(0);
#define chmax(a, b) (a = (b > a ? b : a))
#define chmin(a, b) (a = (b < a ? b : a))
using namespace std;
void trace_() {cerr << "\n";}
template<typename T1, typename... T2> void trace_(T1 t1, T2... t2) {cerr << ' ' << t1; trace_(t2...); }
#define trace(...) cerr << "[" << #__VA_ARGS__ << "] :", trace_(__VA_ARGS__);
int a, b, dp[20][20];
inline void init() {
for (int i = 0; i <= 9; i++) dp[0][i] = 1;
for (int i = 1; i <= 9; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
if (abs(j - k) >= 2) dp[i][j] += dp[i-1][k];
}
}
}
}
inline int sol(int n) {
if (n <= 9) return n;
vector<int> lim;
while (n) {
lim.eb(n % 10);
n /= 10;
}
int res = 0, m = sz(lim);
// 最高位為 0
for (int i = 0; i < m - 1; i++) {
for (int j = 1; j <= 9; j++)
res += dp[i][j];
}
// 最高位小於 lim[m-1]
for (int i = 1; i < lim[m-1]; i++) {
res += dp[m-1][i];
}
// 最高位等於 lim[m-1]
for (int i = m - 2; i >= 0; i--) {
for (int j = 0; j < lim[i]; j++) {
if (abs(lim[i+1] - j) >= 2)
res += dp[i][j];
}
if (abs(lim[i] - lim[i+1]) < 2) break;
if (i == 0) res++; // n 本身也是windy數
}
return res;
}
signed main() {
IO;
cin >> a >> b;
init();
cout << sol(b) - sol(a - 1) << '\n';
}
```
:::
---
## 優化
### 單調對列優化
#### 1. [CF 1107F2](https://codeforces.com/contest/1077/problem/F2)
**解題心得** : 坑
### 矩陣快速幂優化
其實大部分的DP都可以寫成矩陣的形式,但因為如果用矩陣轉移的話通常複雜度都會變成 $O(N^3)$ 以上,所以會用到這種技巧的題目矩陣通常不大,但要轉移非常多次。
#### 1. [DMOJ 0-1](https://dmoj.ca/problem/canada21p5)
**解題心得** : 這題應該很明顯看出他是矩陣快速幂優化?因為$n$很小$K$卻很大,其實我也只寫過這一題😂😂。然後不難想到,狀態可以用 $dp[i][j]$ 經過 $j$ 次操作後,與 $t$ 相差 $i$ 個 bit 的狀況有多少種可能,然後轉移就是一些排列組合可以做到$O(n^2)$。但這樣轉移 $K$ 次的話就會變成 $O(n ^ {2}K)$ 很明顯不太優,主要是因為 $K$ 太大,所以就改用矩陣寫,轉移複雜度變 $O(n ^ 3)$ 但總時間複雜度降到 $O(n ^ {3}log K)$,就可以解決這題了。
::: spoiler code
```cpp=
#define ll loli
#include<bits/stdc++.h>
#define pb push_back
#define eb emplace_back
#define push emplace
#define lb(x, v) lower_bound(all(x), v)
#define ub(x, v) upper_bound(all(x), v)
#define sz(x) (int)(x.size())
#define re(x) reverse(all(x))
#define uni(x) x.resize(unique(all(x)) - x.begin())
#define all(x) x.begin(), x.end()
#define mem(x, v) memset(x, v, sizeof x);
#define int long long
#define pii pair<int, int>
#define inf 1000000000
#define INF 1000000000000000000
#define mod 1000000007
#define F first
#define S second
#define IO ios_base::sync_with_stdio(0); cin.tie(0);
#define chmax(a, b) (a = (b > a ? b : a))
#define chmin(a, b) (a = (b < a ? b : a))
using namespace std;
void trace_() {cerr << "\n";}
template<typename T1, typename... T2> void trace_(T1 t1, T2... t2) {cerr << ' ' << t1; trace_(t2...); }
#define trace(...) cerr << "[" << #__VA_ARGS__ << "] :", trace_(__VA_ARGS__);
const int mxN = 200;
int n, J[mxN], invJ[mxN], x, K;
bitset<mxN> s, t, tmp;
int fp(int a, int b, int p) {
int res = 1;
while(b) {
if(b&1) res = (res * a) % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void build(int n) {
J[1] = J[0] = invJ[1] = invJ[0] = 1;
for(int i = 2; i <= n; i++) J[i] = J[i - 1] * i % mod;
invJ[n] = fp(J[n], mod - 2, mod);
for(int i = n - 1; i >= 2; i--) invJ[i] = invJ[i + 1] * (i + 1) % mod;
}
int C(int n, int m) {
if (n < m) return 0;
if(n == m or m == 0) return 1;
int res = J[n] * invJ[n - m] % mod * invJ[m] % mod;
return res;
}
struct mat {
int n;
vector<vector<int>> m;
mat(int _n): n(_n) {
m.resize(n, vector<int>(n, 0));
}
void init() {
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) m[i][j] = 0;
for(int i = 0; i < n; i++) m[i][i] = 1;
}
void clear() {
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) m[i][j] = 0;
}
mat operator * (mat &a) {
mat t(n);
for(int i = 0; i < a.n; i++)
for(int k = 0; k < a.n; k++)
for(int j = 0; j < a.n; j++)
t.m[i][j] = (t.m[i][j] + m[i][k] * a.m[k][j]) % mod;
return t;
}
mat operator ^ (int x) {
mat t(n), b = *this; t.init();
while (x) {
if (x & 1) t = t * b;
b = b * b;
x >>= 1;
}
return t;
}
void print() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cout << m[i][j] << ' ';
}
cout << '\n';
}
cout << '\n';
}
};
signed main() {
IO;
build(200);
cin >> n >> x >> K >> s >> t;
tmp = s ^ t;
int d = tmp.count(), b = fp(C(n, x), K, mod), invb = fp(b, mod-2, mod);
mat a(n+1);
for (int i = 0; i <= n; i++) {
for (int j = i - x, k = 0; j <= i + x; j += 2, k++) {
if (j >= 0 and j <= n) {
a.m[j][i] += C(i, x - k) * C(n - i, k) % mod;
if (a.m[j][i] > mod) a.m[j][i] -= mod;
}
}
}
mat ans = a ^ K;
int res = ans.m[0][d];
cout << (res * invb) % mod << '\n';
}
```
:::
用了矩陣模板還有線性蓋$C$模板,所以code有點冗