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行的放置情况

  1. 不放的情况 这时候我们可以直接由f[i-1][j][k]转移过来

  2. 放一个的情况,并且放在了之前没有炮的列上,由于我们在没有炮的一列放了一个,会使放一个炮的列数加1,

    这种没放炮的列一共有\(m-j-k+1\)列,统计一下就行了

  3. 放一个炮,并且放在了之前就有一个炮的列上,我们在一个有一个炮的列上放了一个,就会导致放两个炮的列加一.

    放一个炮列的减一,再加上有j-1列可以放。就是 f[i-1][j+1][k-1] * (j+1)

  4. 放两个炮的情况,两个炮都放在没有炮的列上,会导致放一个炮的列加二,并且这样的列一共有\(m-j-k+2\)

    利用组合数算出答案就解决了方程就是 f[i-1][j-2][k] * calc(m-j-k+2,2)

  5. 放两个炮的情况,都放在之前放过两个炮的列上,会导致放两个炮的列加二,放一个炮的列数减二,再加上这样的列

    一共有\(j+2\)列。 方程就可以写成 f[i-1][j+2][k-2] * calc(j+2,2)

  6. 放两个炮的情况,且一个放在之前没有炮的地方,另一个放在之前有一个炮的地方,这就会导致放两个炮的数量加一。

    放一个炮的列数不变(加一后又减一) 再加上有\(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;
}

ENDING

posted @ 2020-08-11 20:53  genshy  阅读(171)  评论(0编辑  收藏  举报