JZOJ 6873. 【2020.11.19提高组模拟】飞翔的鸟(矩阵乘法)
JZOJ 6873. 【2020.11.19提高组模拟】飞翔的鸟
题解
- n , k n,k n,k的数据范围看起来就知道要用矩乘,但障碍的位置是不确定的。
- 如果暴力枚举 n − 2 n-2 n−2次障碍的位置,分别计算每种情况障碍左右两边的方案数,再左右相乘起来,每种情况相加求平均数,可以通过部分的数据。
- 这样显然过不了,考虑能否一次做完?
- 其实可以每个位置设两个状态 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 x−1,x,x+1,而第三种要从障碍左边那列转移到障碍右边,枚举左边 x − 1 , x , x + 1 x-1,x,x+1 x−1,x,x+1,右边 x − 1 , x , x + 1 x-1,x,x+1 x−1,x,x+1, 3 ∗ 3 = 9 3*3=9 3∗3=9种转移,而障碍那列直接跳过,
- 于是这样最后矩乘做 n − 2 n-2 n−2次。
- 但常数比较大,可能需要优化,矩乘中省去一些没有用的转移即可。
代码
#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;
}
哈哈哈哈哈哈哈哈哈哈