洛谷·[AHOI2009]中国象棋

本文详细解析洛谷P2051题目的动态规划解法,探讨在N行M列的棋盘上放置炮的策略,确保炮之间不会互相攻击,通过分析不同状态的转移,计算所有合法放置方案的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初见安~好久没写博客了哈哈哈过暑假去了:)

这里是传送门:洛谷P2051

题目描述

这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

输入格式

一行包含两个整数N,M,之间由一个空格隔开。

输出格式

总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。

输入 

1 3

输出 

7

说明/提示

样例说明

除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有2*2*2-1=7种方案。

数据范围

100%的数据中N和M均不超过100

50%的数据中N和M至少有一个数不超过8

30%的数据中N和M均不超过6

题解

一看就是要用dp的题。求方案数这种东西不是数论就是dp……

考虑dp的话,如果我们以行为阶段,那么可以直接保证每一行最多两个炮;而后的状态就是在哪里放,我们可以考虑每一列的状态——三种,全空,有一个炮和有两个炮。并且我们只考虑数量不考虑这些列的位置,所以就可以得出状态:dp[i][j][k]表示到第i行,有j列只有1个炮,有k列有两个炮时的方案数。【这样设置看起来很奇怪……反正我没想到】转移起来,我们就会考虑到6种情况:

 

一、一个都不放

直接dp[i][j][k] += dp[i - 1][j][k]

二、放一个

1.放在空的列上:dp[i][j][k] += dp[i - 1][j - 1][k] *(m - j - k + 1),因为多了一列有一个炮的,情况数为空列的数量,作差得到。

2.放在有一个炮的列上:dp[i][j][k] += dp[i - 1][j + 1][k - 1] *(j + 1),相当于用一个一列一个的换成了一个一列两个的。

三、放两个

1.都放在空的列上:dp[i][j][k] += dp[i - 1][j - 2][k] * C_{m - k - j + 2}^{2},道理同二1。因为放两个所以涉及到组合数。

2.一个放在一个一列上,一个放在空列上:dp[i][j][k] += dp[i - 1][j][k - 1] * j * (m - j - k +1)

3.都放在一个一列的上面:dp[i][j][k] += dp[i - 1][j + 2][k - 2] * C_{j +2}^2

 

综上就是六种情况……看起来真的很恶心对不对!!!!我有可能有打错了的地方,望周正:)

所以在上面的条件枚举完了过后我们统计一下ans就差不多了。

ans统计哪里?

只要是到了第n行的合法状态都可以。

 

最后提醒,因为如果一直求组合数的话可能会很麻烦,而且都是取两个,这就很方便了我们直接化简一下组合数的过程:

C_n^2 = \frac{n!}{2*(n-2)!}=\frac{n*(n-1)}{2}

重点就是这些了。:)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 105
using namespace std;
const int mod = 9999973;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, m;
ll dp[maxn][maxn][maxn], ans;
int main() {
	n = read(), m = read();
	dp[0][0][0] = 1;
	for(int i = 1; i <= n; i++) {//建议不要看这一大坨,看上面的情况自己写吧。
		for(int j = 0; j <= m; j++) {
			for(int k = 0; j + k <= m; k++) {
				dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % mod;
				if(j > 0) dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k] * (m - j - k + 1)) % mod;
				if(k > 0) dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j + 1][k - 1] * (j + 1) + dp[i - 1][j][k - 1] * j * (m - j - k + 1)) % mod;
				if(j > 1) dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j - 2][k] * (m - j - k + 2) * (m - j - k + 1) / 2)) % mod; 
				if(k > 1) dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j + 2][k - 2] * (j + 2) * (j + 1) / 2)) % mod;
			}
		}
	}
	
	for(int j = 0; j <= m; j++) for(int k = 0; j + k <= m; k++) ans += dp[n][j][k], ans %= mod;
	printf("%lld\n", ans);
	return 0;
}

有史以来注释最少的一次题解哈哈哈哈哈……

迎评:)
——End——

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值