JZOJ 6873. 【2020.11.19提高组模拟】飞翔的鸟(矩阵乘法)

JZOJ 6873. 【2020.11.19提高组模拟】飞翔的鸟

题解

  • n , k n,k n,k的数据范围看起来就知道要用矩乘,但障碍的位置是不确定的。
  • 如果暴力枚举 n − 2 n-2 n2次障碍的位置,分别计算每种情况障碍左右两边的方案数,再左右相乘起来,每种情况相加求平均数,可以通过部分的数据。
  • 这样显然过不了,考虑能否一次做完?
  • 其实可以每个位置设两个状态 0 / 1 0/1 0/1分别表示是否经过障碍,然后分三种情况, 0 0 0转移到 0 0 0 1 1 1转移到 1 1 1 0 0 0转移到 1 1 1
  • 其中前两种可以直接按题意从 x x x转移到 x − 1 , x , x + 1 x-1,x,x+1 x1,x,x+1,而第三种要从障碍左边那列转移到障碍右边,枚举左边 x − 1 , x , x + 1 x-1,x,x+1 x1,x,x+1,右边 x − 1 , x , x + 1 x-1,x,x+1 x1,x,x+1 3 ∗ 3 = 9 3*3=9 33=9种转移,而障碍那列直接跳过,
  • 于是这样最后矩乘做 n − 2 n-2 n2次。
  • 但常数比较大,可能需要优化,矩乘中省去一些没有用的转移即可。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 265
#define ll unsigned long long
#define md 1000000007
int n, m, A, B;
ll c[N][N], f[N][N], g[N][N];
ll qpw(ll x, ll y) {
	if(!y) return 1;
	ll l = qpw(x, y / 2);
	if(y % 2) return l * l % md * x % md;
	return l * l % md;
}
void ksm(int n) {
	if(n == 0) {
		for(int i = 1; i <= m * 2; i++) f[i][i] = 1;
	}
	else {
		ksm(n / 2);
		memset(g, 0, sizeof(g));
		for(int i = 1; i <= m * 2; i++) 
			for(int j = (i > m) ? (m + 1) : 1; j <= m * 2; j++) 
				for(int k = (i > m) ? (m + 1) : 1; k <= ((j > m) ? m * 2 : m); k++) {
					if(k % 18 == 0) g[i][j] %= md;
					g[i][j] += f[i][k] * f[k][j];
				}
		for(int i = 1; i <= m * 2; i++)
			for(int j = 1; j <= m * 2; j++) f[i][j] = g[i][j] % md;
		if(n % 2) {
			memset(g, 0, sizeof(g));
			for(int i = 1; i <= m * 2; i++) 
				for(int j = (i > m) ? (m + 1) : 1; j <= m * 2; j++) {
					int x = j;
					if(x > m) x -= m;
					for(int k = max(x - 2, 0); k <= min(x + 2, m * 2); k++) g[i][j] += f[i][k] * c[k][j];
					x += m;
					for(int k = max(x - 2, 0); k <= min(x + 2, m * 2); k++) g[i][j] += f[i][k] * c[k][j];
				}
			for(int i = 1; i <= m * 2; i++)
				for(int j = 1; j <= m * 2; j++) f[i][j] = g[i][j] % md;	
		}
	}
}
int main() {
	int i, j, k;
	scanf("%d%d%d%d", &n, &m, &A, &B);
	for(i = 1; i <= m + m; i++) {
		if(i == m) c[i][i] = c[i][i - 1] = 1;
		else if(i == m + 1) c[i][i] = c[i][i + 1] = 1;
		else c[i][i] = c[i][i + 1] = c[i][i - 1] = 1;
	}
	for(i = A + 1; i < B; i++) 
		for(j = -1; j < 2; j++) if(i + j <= m)
			for(k = -1; k < 2; k++) if(i + k) c[i + j][i + m + k]++;
	ksm(n - 2);
	printf("%lld\n", f[m / 2][m / 2 * 3] * qpw(n - 2, md - 2) % md);
	return 0;
}
posted @ 2021-01-07 20:13  AnAn_119  阅读(115)  评论(0编辑  收藏  举报