uva 12371 Guards (dp)

题目链接: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\n", kase, dp[n][k]);
	}
	return 0;
}
posted @ 2021-08-07 22:41  Tartarus_li  阅读(59)  评论(0编辑  收藏  举报