问题描述
小明很喜欢国际象棋,尤其喜欢国际象棋里面的大象(只要无阻挡能够斜着走任意格),但是他觉得国际象棋里的大象太凶残了,于是他想到了小象, 小象就没有大象那么凶残,它的攻击范围是它当前格子直角所斜对的格子。现在小明要在棋盘上放很多个小象,有趣的是,当两个小象所在格子有公共边时, 它们将合体变成合体象,多个小象满足条件也会合体,合体象的攻击范围也是它所覆盖格子区域直角所斜对的格子,现在要求任何一个象的攻击范围上是空的(即不摆放棋子), 小明的棋盘很特殊,有m∗n个格子,求满足条件的摆放的方案数,由于方案数太大,需要对1000000007取模。 下面给出几种形状下的象的攻击范围图,叉号表示攻击范围。
输入描述
输入有多组数据(最多5组),每组数据有两个整数n,m含义如题目描述。 1≤m≤7,1≤n≤1000000000
输出描述
每组数据对应输出一行包含一个整数,表示满足条件的摆放的方案数。
输入样例
1 1 2 3
输出样例
2 50
因为m比较小,可以对列进行状压,用一个数字代表一个列的状态,然后如果可以从状态i转移到状态j,那么添加一条i道到j的边,题目要求的是n列有多少种方法,其实就是问建好的图中有多少种路径长度为n-1的走法,这个可以由最开始构造的可达矩阵乘方n-1次得到,将最后的矩阵求和一边就好。
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
const int maxn = 130;
int n, m;
struct mat
{
int num[maxn][maxn];
mat()
{
memset(num, 0, sizeof(mat));
}
mat operator * (const mat& c)const
{
mat ans;
int col = 1 << m;
for (int i = 0; i < col; i++)
for (int k = 0; k < col; k++)
{
if (num[i][k] == 0) continue;
for (int j = 0; j < col; j++)
ans.num[i][j] = (ans.num[i][j] + (ll)num[i][k] * c.num[k][j] % mod) % mod;
}
return ans;
}
};
mat pow(mat x, int k)
{
mat ans;
for (int i = 0; i < (1 << m); i++) ans.num[i][i] = 1;
while (k)
{
if (k & 1) ans = ans * x;
x = x * x;
k >>= 1;
}
return ans;
}
int ok(int s1, int s2)
{
for (int i = 0; i < m; i++)
{
if (s2 & (1 << i))
{
if ((1 << i) & s1) continue;
if (i && !(s2 & (1 << (i - 1))) && (s1 & (1 << (i - 1)))) return 0;
if (i != (m - 1) && !(s2 & (1 << (i + 1))) && (s1 & (1 << (i + 1)))) return 0;
}
}
return 1;
}
int main()
{
while (scanf("%d%d", &n, &m) != EOF)
{
mat a;
int col = 1 << m;
for (int i = 0; i < col; i++)
for (int j = 0; j < col; j++)
a.num[i][j] = ok(i, j);
a = pow(a, n - 1);
int ans = 0;
for (int i = 0; i < col; i++)
for (int j = 0; j < col; j++)
ans = (ans + a.num[i][j]) % mod;
printf("%d\n", ans);
}
return 0;
}