我是常年游荡于题解区的幽灵。
直观的想法就是前缀和+差分优化DP, 但还有些比较奇妙的方法or trick:
-
(f[i][j] = f[i-1][j-1]+f[i-1][j]+f[i-1][j+1]+color{red}{f[i-2][j]}), 因为可以一步跳到 (f[i][j]) 的状态也可以一步跳到 (f[i-2][j]), 反过来也成立 。
-
只考虑从左边某个特定的列的转移矩阵, 设为 (J), 设第 (i) 列的答案矩阵是 (A_i), 则有:(A_n = J*(A_{n-1}+A_{n-3}+A_{n-5}+cdots)) 且 (A_{n-2} = J*(A_{n-3}+A_{n-5}+A_{n-7}+cdots)), 不难得出 (A_n = J*A_{n-1}+A_{n-2}), 这也是个递推, 构造矩阵:
[[
egin{matrix}
J & E\
E & O
end{matrix}
]
*
[
egin{matrix}
A_n\
A_{n+1}
end{matrix}
]=[
egin{matrix}
A_{n+1}\
A_{n}
end{matrix}
]
]
其中 (E) 是单位矩阵而 (O) 是全零矩阵,就可以大力递推了。
- 这个, 建图
接下来选择性地实现/口胡上述的某些解法(我挺中意矩阵套矩阵的解法)
直观解法
s0[i]
维护与当前列差偶数列的第 i
行的方案数之和, s1[i]
则是相差奇数列的。
考虑转移到下一列, 则有:
s0[i] <- s1[i] + s0[i-1] + s0[i] + s0[i+1]
s1[i] <- s0[i]
矩阵套矩阵
#include<bits/stdc++.h>
using namespace std;
const int mo = 30011;
int n,m;
int qm(int x) { return x>=mo?x-mo:x; }
struct M{
int t[51][51];
M() {memset(t,0,sizeof t); }
};
M operator*(const M &a, const M &b) {
M c; for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=n;++k)
c.t[i][j] = qm(c.t[i][j] + a.t[i][k]*b.t[k][j]%mo);
return c;
}
M operator+(const M &a, const M &b) {
M c; for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)
c.t[i][j] = qm(a.t[i][j] + b.t[i][j]);
return c;
}
struct M2{ M t[3][3]; } S,T,Delta;
M2 operator*(const M2 &a, const M2 &b) {
M2 c; for(int i=1;i<=2;++i)for(int j=1;j<=2;++j)for(int k=1;k<=2;++k)
c.t[i][j]= c.t[i][j] + a.t[i][k]*b.t[k][j];
return c;
}
int main()
{
scanf("%d%d",&n,&m);
S.t[1][1].t[1][1] = S.t[1][1].t[2][1] = 1;//µÚ¶þÁеĴ𰸾ØÕó
if(m==2) return printf("%d",S.t[1][1].t[n][1]),0;
for(int i=1;i<=n;++i) {
T.t[1][2].t[i][i] = T.t[2][1].t[i][i] = 1;
T.t[1][1].t[i][i-1] = T.t[1][1].t[i][i] =T.t[1][1].t[i][i+1] = 1;
}
m -= 3;
Delta = T;
while(m)
{
if(m&1) Delta = Delta * T;
T = T*T;
m >>= 1;
}
S = Delta*S;
printf("%d",S.t[1][1].t[n][1]);
return 0;
}