SHOI2013 超级跳马

我是常年游荡于题解区的幽灵。

直观的想法就是前缀和+差分优化DP, 但还有些比较奇妙的方法or trick:

  1. \(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]\), 反过来也成立 。

  2. 只考虑从左边某个特定的列的转移矩阵, 设为 \(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}\), 这也是个递推, 构造矩阵:

\[[ \begin{matrix} J & E\\ E & O \end{matrix} ] * [ \begin{matrix} A_n\\ A_{n+1} \end{matrix} ]=[ \begin{matrix} A_{n+1}\\ A_{n} \end{matrix} ] \]

其中 \(E\) 是单位矩阵而 \(O\) 是全零矩阵,就可以大力递推了。

  1. 这个, 建图

接下来选择性地实现/口胡上述的某些解法(我挺中意矩阵套矩阵的解法)


直观解法

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;
}
posted @ 2020-11-25 11:02  xwmwr  阅读(64)  评论(0编辑  收藏  举报