题目链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3793
首先发现按照规定的摆放方法一定会有圈,其中每个圈的相邻两行都有一对棋子位于同一列上
设 (dp[i][j]) 表示 (i*i) 的棋盘上有 (j) 个圈的方案数,则转移的时候分增不增加圈数来讨论
-
不增加圈数
分 (i*i) 这个格子有没有守卫来讨论
如果有,只需从前 (i-1) 行中选一行,将右侧的守卫移动到最后一列,再在最后一行的相应位置增加一个守卫即可,对应的转移方程为 (dp[i][j] += dp[i-1][j]*(i-1))
如果没有,需要从前 (i-1) 列选择一列,将该列两个守卫移动到最后一列中同样的位置,然后在前 (i-1) 行选择一行,将左侧的守卫移动到之前选择的一列的最后一行,再加上最后一行剩余的守卫即可,对应的转移方程为 (dp[i][j] += dp[i-1][j] * (i-1) * (i-1))
-
增加圈数
由上面的讨论可知,还有一种情况是增加一个 (2*2) 的圈,方法是在最后一行选择两个位置作为下半部分,再在前 (i-1) 行选择一行作为上半部分,对应的转移方程为 (dp[i][j] += dp[i-2][j-1] * C_n^2 * (i-1))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100010;
const int maxk = 55;
const int M = 1000000007;
int T, n, k;
int dp[maxn][maxk];
int sqr(ll x){
x %= M;
return 1ll * x * x % M;
}
ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }
int main(){
dp[2][1] = 1;
for(int i = 3 ; i <= 100000 ; ++i){
for(int j = 1 ; j <= 50 ; ++j){
dp[i][j] = 1ll * dp[i-1][j] * (i-1) % M * i % M;
dp[i][j] = (dp[i][j] + 1ll * dp[i-2][j-1] * (1ll * i * (i-1)/2) % M * (i-1) % M) % M;
}
}
scanf("%d", &T);
for(int kase = 1 ; kase <= T ; ++kase){
scanf("%d%d", &n, &k);
printf("Case %d: %d
", kase, dp[n][k]);
}
return 0;
}