P2051 [AHOI2009]中国象棋
题目描述
这次小可可想解决的难题和中国象棋有关,在一个 n 行 m 列的棋盘上,让你放若干个炮(可以是 0个),使得没有一个炮可以攻击到另一个炮,
请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,
且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入格式
一行包含两个整数 n,m之间由一个空格隔开。
输出格式
总共的方案数,由于该值可能很大,只需给出方案数模 9999973 的结果。
输入输出样例
输入 #1
1 3
输出 #1
7
说明/提示
样例说明
除了 3 个格子里都塞满了炮以外,其它方案都是可行的,所以一共有 2×2×2−1=7 种方案。
数据规模与约定
对于 30%的数据,n 和 m 均不超过 6。
对于 50% 的数据,n 和 m 至少有一个数不超过 8。
对于 100% 的数据,1≤n,m≤100。
对于前30%的数据,我们可以爆搜拿部分暴力分。
然后,我们就需要开始考虑正解。
首先,根据中国象棋的芝士,每行每列只能放两个炮(这不是废话吗)。
我们就可以根据这个列出方程
我们设 f[i][j][k]表示第i行,其中有j列放了一个棋子,k列放了两个棋子的方案数。
转移:考虑第i行的放置情况
-
不放的情况 这时候我们可以直接由f[i-1][j][k]转移过来
-
放一个的情况,并且放在了之前没有炮的列上,由于我们在没有炮的一列放了一个,会使放一个炮的列数加1,
这种没放炮的列一共有\(m-j-k+1\)列,统计一下就行了
-
放一个炮,并且放在了之前就有一个炮的列上,我们在一个有一个炮的列上放了一个,就会导致放两个炮的列加一.
放一个炮列的减一,再加上有j-1列可以放。就是 f[i-1][j+1][k-1] * (j+1)
-
放两个炮的情况,两个炮都放在没有炮的列上,会导致放一个炮的列加二,并且这样的列一共有\(m-j-k+2\)
利用组合数算出答案就解决了方程就是 f[i-1][j-2][k] * calc(m-j-k+2,2)
-
放两个炮的情况,都放在之前放过两个炮的列上,会导致放两个炮的列加二,放一个炮的列数减二,再加上这样的列
一共有\(j+2\)列。 方程就可以写成 f[i-1][j+2][k-2] * calc(j+2,2)
-
放两个炮的情况,且一个放在之前没有炮的地方,另一个放在之前有一个炮的地方,这就会导致放两个炮的数量加一。
放一个炮的列数不变(
加一后又减一) 再加上有\(j*(k-1)\)中情况,直接计算就okk了
答案就是\(\sum_{i=0}^{m} \sum_{j=0}^{i+j<=m}f[n][i][j]\)
这题,分类讨论确实烦了点,令人想吐
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
const int p = 9999973;
int n,m;
LL c[110][110],f[110][110][110],ans;
void YYCH()
{
c[0][0] = 1;
for(int i = 1; i <= 100; i++)//杨辉三角求组合数
{
c[i][0] = c[i][i] = 1;
for(int j = 0; j <= i; j++)
{
c[i][j] = (c[i-1][j] + c[i-1][j-1]) % p;
}
}
}
LL calc(int n,int m)
{
return c[n][m];
}
int main()
{
scanf("%d%d",&n,&m); YYCH();
f[0][0][0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= m; j++)
{
for(int k = 0; k+j <= m; k++)//分类讨论
{
f[i][j][k] = (f[i][j][k] + f[i-1][j][k]) % p;//不放
if(j-1 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j-1][k] * (m-j-k+1) % p) % p;//放一个棋子在没有棋子的列中
if(k-1 >= 0 && j+1 <= m) f[i][j][k] = (f[i][j][k] + f[i-1][j+1][k-1] * (j+1) % p) % p;//放一个棋子在之前有一个棋子的列中
if(j-2 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j-2][k] * calc(m-j-k+2,2)) % p;//放两个棋子在之前都没有放的列中
if(k-2 >= 0 && j+2 <= m) f[i][j][k] = (f[i][j][k] + f[i-1][j+2][k-2] * calc(j+2,2) % p) % p; //放两个棋子在之前放过一个棋子的列中
if(j-1 >= 0 && k-1 >= 0) f[i][j][k] = (f[i][j][k] + f[i-1][j][k-1] * (j) * (m-j-k+1) % p) % p; //一个放在之前有一个的列中一个放在之前没有棋子的列中
}
}
}
for(int i = 0; i <= m; i++)
{
for(int j = 0; j+i <= m; j++)
{
ans = (ans + f[n][i][j]) % p;
}
}
printf("%lld\n",ans);
return 0;
}