题解 P5343 【【XR-1】分块】

· · 题解

本蒻蒟比赛的时候想不到简单的方法,于是只能死守DP.
首先是处理读入的两行数据,去重。同样只想到了用unique的很暴力的处理手段:

scanf("%lld", &n);
scanf("%lld", &pr);
for(ll i = 1; i <= pr; i++) scanf("%lld", &ava1[i]);
scanf("%lld", &nf);
for(ll i = 1; i <= nf; i++) scanf("%lld", &ava2[i]);
sort(ava1 + 1, ava1 + pr + 1, cmp);
sort(ava2 + 1, ava2 + nf + 1, cmp);
ava1[0] = unique(ava1 + 1, ava1 + pr + 1) - ava1 - 1;
ava2[0] = unique(ava2 + 1, ava2 + nf + 1) - ava2 - 1;
for(int i = 1; i <= ava1[0]; i++) ava[i] = ava1[i];
for(int i = 1; i <= ava2[0]; i++) ava[i + ava1[0]] = ava2[i];
sort(ava + 1, ava + ava1[0] + ava2[0] + 1, cmp);
for(ll i = 1; i <= ava1[0] + ava2[0]; i++)
    if(ava[i] == ava[i - 1] && ava[i + 1] != ava[i])
        ava[++ava[0]] = ava[i];

但这都不是重点,重点是怎么计算左右分块方案,很明显qwq,这道题类似爬楼梯,所以直接暴力转移:

f[i]\text{ 表示到长度 }i\text{ 一共有多少可能的分块数量。} f[i] = \sum^{ava[0]}_{i = 1}f[i - ava[i]]

显然

for(int i = 1; i <= n; i++)
    for(int j = 1; j <= ava[0]; j++)
        f[i] = (f[i] + f[i - ava[j]]) % MOD;

但是这只能过60%的数据...

一看100%的数据规模

1 \leq n \leq 10^{18}

...
...
...

所以我们需要优化qwq

## 矩阵 $$\left[ \begin{array} { c c c c } { a _ { 11 } } & { a _ { 12 } } & { \cdots } & { a _ { 1 n } } \\ { a _ { 21 } } & { a _ { 22 } } & { \cdots } & { a _ { 2 n } } \\ { \vdots } & { \vdots } & { \ddots } & { \vdots } \\ { a _ { n 1 } } & { a _ { n 2 } } & { \cdots } & { a _ { n n } } \end{array} \right]$$ 矩阵满足的运算有加法,减法,乘法和其他一些... 但是我们关注的是矩阵乘法。矩阵的乘法是有条件的,如果两个矩阵A和B,那么只有当A的列数和B的行数相同时才可以乘法,乘法满足: 设$A$为$m \times p$的矩阵,$B$为$p \times n$的矩阵,那么称$m \times n$的矩阵C为矩阵A与B的乘积,记作$C=AB$ ,其中矩阵$C$中的第$i$行第$j$列元素可以表示为: $$( A B ) _ { i j } = \sum _ { k = 1 } ^ { p } a _ { i k } b _ { k j } = a _ { i 1 } b _ { 1 j } + a _ { i 2 } b _ { 2 j } + \cdots + a _ { i p } b _ { p j }$$ 举个栗子: $$C = A B = \left( \begin{array} { l l l } { 1 } & { 2 } & { 3 } \\ { 4 } & { 5 } & { 6 } \end{array} \right) \left( \begin{array} { l l } { 1 } & { 4 } \\ { 2 } & { 5 } \\ { 3 } & { 6 } \end{array} \right) = \left( \begin{array} { c c } { 1 \times 1 + 2 \times 2 + 3 \times 3 } & { 1 \times 4 + 2 \times 5 + 3 \times 6 } \\ { 4 \times 1 + 5 \times 2 + 6 \times 3 } & { 4 \times 4 + 5 \times 5 + 6 \times 6 } \end{array} \right) = \left( \begin{array} { c c } { 14 } & { 32 } \\ { 32 } & { 77 } \end{array} \right)$$ 而且重要的是矩阵满足结合律,分配律。 所以... 就做出来啦! 先举个小栗子,菲波那切数列: $$f(i) = f(i - 1) + f(i - 2)$$ 可以用递归或递推$O(n)$求出来,但如果我让你写$O(log n)$的算法,该怎么办呢? 矩阵就派上用场啦: 如果我有一个矩阵 $$\left[ \begin{array} { l } { f ( n - 1 ) } \\ { f ( n - 2 ) } \end{array} \right]$$ 你能把它变成 $$\left[ \begin{array} { l } { f ( n ) } \\ { f ( n - 1 ) } \end{array} \right]$$ 吗? 首先 $$\begin{aligned} f(n) &= f(n - 1) + f(n - 2)\\ f(n - 1) &= f(n - 1)\\ \end{aligned}$$ 所以不难发现,我们只要乘上一个矩阵就可以啦: $$\left[ \begin{array} { l } { f ( n ) } \\ { f ( n - 1 ) } \end{array} \right] = \left[ \begin{array} { l l } { 1 } & { 1 } \\ { 1 } & { 0 } \end{array} \right] \left[ \begin{array} { l } { f ( n - 1 ) } \\ { f ( n - 2 ) } \end{array} \right]$$ 代码实现: ```cpp struct mat{ ll r, c; ll m[MAXM][MAXM]; void clear(){memset(m, 0, sizeof(m));} }; mat matMul(mat m1, mat m2){ mat ret; ret.r = m1.r; ret.c = m2.c; for(int i = 1; i <= ret.r; i++) for(int j = 1; j <= ret.c; j++){ ll t = 0; for(int k = 1; k <= m1.c; k++) t = (t + (m1.m[i][k] * m2.m[k][j]) % MOD) % MOD; ret.m[i][j] = t; } return ret; } ``` 但是一个一个程,复杂度还是$O(n)$的,甚至因为矩阵,复杂度常数变大了,那要这个玩意儿还有什么用? 嗯,看起来没用,所以... 等一下!矩阵满足交换结合律,而且$O(\log n)$怎么听起来这么像我们以前学过的一个叫内个什么算法: $$\Large\text{快速幂}$$ 隆重介绍:矩阵快速幂!!! ## 矩阵快速幂 我们乘上$n$个相同的矩阵,相当于$O(\log n)$求出该矩阵的$n$次方,随后再乘上初始值,即: $$\left[ \begin{array} { l } { f ( n ) } \\ { f ( n - 1 ) } \end{array} \right] = \left[ \begin{array} { c c } { 1 } & { 1 } \\ { 1 } & { 0 } \end{array} \right] \left[ \begin{array} { c c } { f ( n - 1 ) } \\ { f ( n - 2 ) } \end{array} \right] = \left[ \begin{array} { c c } { 1 } & { 1 } \\ { 1 } & { 0 } \end{array} \right] ^ { 2 } \left[ \begin{array} { c c } { f ( n - 2 ) } \\ { f ( n - 3 ) } \end{array} \right] = \cdots = \left[ \begin{array} { c c } { 1 } & { 1 } \\ { 1 } & { 0 } \end{array} \right] ^ { n - 2 } \left[ \begin{array} { c } { f ( 2 ) } \\ { f ( 1 ) } \end{array} \right]$$ 模板如下: ```cpp mat qpow(mat x, ll powers){ mat ret; ret.clear(); ret.r = x.r; ret.c = x.c; for(ll i = 1; i <= ret.r; i++) ret.m[i][i] = 1; while(powers){ if(powers & 1) ret = matMul(ret, x); x = matMul(x, x); powers >>= 1; } return ret; } ``` 要注意模板中的单位矩阵不是: $$\left[ \begin{array} { c c c c } { 1 } & { 1 } & { \cdots } & { 1 } \\ { 1 } & { 1 } & { \cdots } & { 1 } \\ { \vdots } & { \vdots } & { \ddots } & { \vdots } \\ { 1 } & { 1 } & { \cdots } & { 1 } \end{array} \right]$$ 单位矩阵应该是: $$\left[ \begin{array} { c c c c } { 1 } & { 0 } & { \cdots } & { 0 } \\ { 0 } & { 1 } & { \cdots } & { 0 } \\ { \vdots } & { \vdots } & { \ddots } & { \vdots } \\ { 0 } & { 0 } & { \cdots } & { 1 } \end{array} \right]$$ 自己乘一乘就知道了qwq。 扩大到最多100阶的“菲波那切数列”而已,也就很容易了。 矩阵第一行是你要加的位置为1,其余为0。剩余的是对角线为1,偏移1位。 AC代码如下: ```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; const ll MAXM = 110; const ll MOD = 1e9 + 7; ll n, pr, nf, ava1[MAXM], ava2[MAXM], ava[MAXM << 1 | 1], ans; struct mat{ ll r, c; ll m[MAXM][MAXM]; void clear(){memset(m, 0, sizeof(m));} }; mat matMul(mat m1, mat m2); void prt(mat m); mat qpow(mat x, ll powers); bool cmp(const ll &x, const ll &y){return x < y;} int main(){ scanf("%lld", &n); scanf("%lld", &pr); for(ll i = 1; i <= pr; i++) scanf("%lld", &ava1[i]); scanf("%lld", &nf); for(ll i = 1; i <= nf; i++) scanf("%lld", &ava2[i]); sort(ava1 + 1, ava1 + pr + 1, cmp); sort(ava2 + 1, ava2 + nf + 1, cmp); ava1[0] = unique(ava1 + 1, ava1 + pr + 1) - ava1 - 1; ava2[0] = unique(ava2 + 1, ava2 + nf + 1) - ava2 - 1; for(int i = 1; i <= ava1[0]; i++) ava[i] = ava1[i]; for(int i = 1; i <= ava2[0]; i++) ava[i + ava1[0]] = ava2[i]; sort(ava + 1, ava + ava1[0] + ava2[0] + 1, cmp); for(ll i = 1; i <= ava1[0] + ava2[0]; i++) if(ava[i] == ava[i - 1] && ava[i + 1] != ava[i]) ava[++ava[0]] = ava[i]; if(ava[0] == 0) {printf("0\n"); return 0;} mat ans; ans.clear(); ans.c = ans.r = ava[ava[0]]; //建立矩阵 for(ll i = 1; i <= ava[0]; i++) ans.m[1][ava[i]] = 1; for(ll i = 2; i <= ava[ava[0]]; i++) ans.m[i][i - 1] = 1; ans = qpow(ans, n);//快速幂 printf("%lld\n", ans.m[1][1]); //自己手算一下,就发现只要输出左上角就可以了 return 1; //抄答案的是坏孩纸哦qwq } mat matMul(mat m1, mat m2){ mat ret; ret.r = m1.r; ret.c = m2.c; for(int i = 1; i <= ret.r; i++) for(int j = 1; j <= ret.c; j++){ ll t = 0; for(int k = 1; k <= m1.c; k++) t = (t + (m1.m[i][k] * m2.m[k][j]) % MOD) % MOD; ret.m[i][j] = t; } return ret; } void prt(mat m){ for(ll i = 1; i <= m.c; i++){ for(ll j = 1; j <= m.r; j++) printf("%lld ", m.m[i][j]); putchar('\n'); } putchar('\n'); } mat qpow(mat x, ll powers){ mat ret; ret.clear(); ret.r = x.r; ret.c = x.c; for(ll i = 1; i <= ret.r; i++) ret.m[i][i] = 1; while(powers){ if(powers & 1) ret = matMul(ret, x); x = matMul(x, x); powers >>= 1; } return ret; } ``` 总复杂度$O(x^3 \log n)$,最慢的点不开$O2$是$764ms$。 [我的提交](https://www.luogu.org/recordnew/show/18780010) 谢谢阅读我的第二篇题解 有疑问或错误请在评论区指出哦qwq