P2051 [AHOI2009]中国象棋

洛谷题面

妙题。

题目大意

在一个 \(n\)\(m\) 列的棋盘上,让你放若干个炮(可以是 \(0\) 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。

大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好有一个棋子。

方案数对 \(\textbf{9999973}\) 取模。

题目分析

\(dp[i,j,k]\) 表示放了前 \(i\) 行,有 \(j\) 列是有一个棋子,有 \(k\) 列是有 2 个棋子的合法方案数。

根据定义,初始化 \(dp[0,0,0]=1\)(显然是唯一可确定的),答案为 \(\sum\limits_{i=0}^m\sum\limits_{j=0}^mdp[n,i,j]\)

来推一推式子,比较麻烦,特别是边界问题。

为了保证合法性,我们显然不会在已有两个棋子的列上再去添加任何棋子,所以状态转移是有限的。为方便,下面定义有一个棋子的列为“一棋列”,有两个棋子的列为“两棋列”,没有棋子的列为“无棋列”。

不选任何棋子时,直接继承上一状态,方程为 \(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j,k]\)

放一个棋子时,有两种情况:

放在已有的“一棋列”里,少了一列“一棋列”,多了一列“两棋列”,也就是由 \(dp[i-1,j+1,k-1]\) 转移而来,由于是从上一个状态转移而来的,上一个转移状态的“一棋列”个数为 \(j+1\),所以有 \(j+1\) 种选择。方程为 \(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j+1,k-1]\times (j+1)\)

放在已有的“无棋列”里,多了一列“一棋列”,由 \(dp[i-1,j-1,k]\) 转移而来,上个状态的方案数为 \((m-(j-1)-k)\)(也就是“无棋列”的数量),方程为:\(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j-1,k]\times(m-(j-1)-k)\)

放两个棋子时,有三种情况:

两个均放在“一棋列”,少了两个“一棋列”,多了两个“两棋列”,由 \(dp[i-1,j+2,k-2]\) 转移而来,上个状态的方案数为 \(C_{j+2}^2\)(也就是从“一棋列”中选两个),方程为:\(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j+2,k-2]\times C_{j+2}^2\)

两个均放在“无棋列”,多了两个“一棋列”,由 \(dp[i-1,j-2,k]\) 转移而来,上个状态的方案数为 \(C_{m-(j-2)-k}^2\)(也就是从“无棋列”中选两个),方程为:\(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j+2,k-2]\times C_{m-(j-2)-k}^2\)

一个放在“无棋列”,一个放在“一棋列”,多了一个“一棋列”又少了一个“一棋列”抵消了,多了一个“两棋列”,由 \(dp[i-1,j,k-1]\) 转移来,方案数为 \(j\times (m-j-(k-1))\),方程为 \(dp[i,j,k]\gets dp[i,j,k]+dp[i-1,j,k-1]\times j\times(m-j-(k-1))\)


其他就是些细节问题了,求组合的公式为:

如果直接暴力每次算的话很慢,如果把阶乘预处理出来的话你会发现会出现除法,而你又使用了 \(\bmod\),所以要用逆元?不用那么麻烦,直接递推即可,\(C_{i}^j=C_{i-1}^j+C_{i-1}^{j-1}\),虽然我知道只需要处理 \(j=2\) 的情况,但我还是全部预处理了出来(

注意开 long long

本着精益求精的精神,我也写了一份逆元求组合数的代码(破罐子破摔再熬一会儿):

inline void init() {
	fac[0] = 1;
	for (register int i = 1;i <= 100; ++ i) fac[i] = fac[i - 1] * i % mod;
}
inline int ksm(int x,int y,int p) {
    int res = 1;
    for (;y;y >>= 1ll) {
        if (y & 1ll) res = res * x % p;
        x = x * x % p;
    }
    return res % p;
}
inline int C(long long x,long long y) {
    int nx = ksm(fac[x - y],mod - 2,mod),ny = ksm(fac[y],mod - 2,mod);
    return MOD(MOD(fac[x] * nx) * ny);
}

代码

//2022/4/2
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#define int long long
#define enter putchar(10)
#define debug(c,que) cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) cout << arr[i] << w;
#define speed_up() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define Abs(x) ((x) > 0 ? (x) : -(x))
#define stop return(0)
const int mod = 9999973;
inline int MOD(int x) {
	if(x < 0) x += mod;
	return x % mod;
}
namespace Newstd {
	char buf[1 << 21],*p1 = buf,*p2 = buf;
	inline int getc() {
		return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin),p1 == p2) ? EOF : *p1 ++;
	}
	inline int read() {
		int ret = 0,f = 0;char ch = getc();
		while (!isdigit(ch)) {
			if(ch == '-') f = 1;
			ch = getc();
		}
		while (isdigit(ch)) {
			ret = (ret << 3) + (ret << 1) + ch - 48;
			ch = getc();
		}
		return f ? -ret : ret;
	}
	inline void write(int x) {
		if(x < 0) {
			putchar('-');
			x = -x;
		}
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
}
using namespace Newstd;
using namespace std;

const int N = 105;
int C[N][N],dp[N][N][N];
//dp[i][j][k]:放了前 i 行,有 j 列是有一个棋子,有 k 列是有 2 个棋子的合法方案数
int n,m;
inline void init() {
	C[0][0] = 1;
	for (register int i = 1;i <= 100; ++ i) {
		C[i][0] = 1;
		for (register int j = 1;j <= i; ++ j) {
			C[i][j] = MOD(C[i - 1][j] + C[i - 1][j - 1]);
		}
	}
}
#undef int
int main(void) {
#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
#endif
	#define int long long
	init();
	n = read(),m = read();
	dp[0][0][0] = 1;
	for (register int i = 1;i <= n; ++ i) {
		for (register int j = 0;j <= m; ++ j) {//枚举一个棋子的列数
			for (register int k = 0;k <= m - j; ++ k) {//枚举两个棋子的列数
				dp[i][j][k] = dp[i - 1][j][k];//不选
				//放一个棋子
				if (k >= 1) dp[i][j][k] = MOD(dp[i][j][k] + dp[i - 1][j + 1][k - 1] * (j + 1));//放在一个棋子的列,现在变成两个棋子
				if (j >= 1) dp[i][j][k] = MOD(dp[i][j][k] + dp[i - 1][j - 1][k] * (m - (j - 1) - k));//放在没有棋子的列,现在变成一个棋子
				//放两个棋子
				if (k >= 1) dp[i][j][k] = MOD(dp[i][j][k] + dp[i - 1][j][k - 1] * j * (m - j - (k - 1)));//一个放在有一个棋子的列,一个放在没有棋子的列
				if (j >= 2) dp[i][j][k] = MOD(dp[i][j][k] + dp[i - 1][j - 2][k] * C[m - (j - 2) - k][2]);//两个都放在没有棋子的列
				if (k >= 2) dp[i][j][k] = MOD(dp[i][j][k] + dp[i - 1][j + 2][k - 2] * C[j + 2][2]);//两个都放在一个格子的列
			}
		}
	}
	int ans = 0;
	for (register int i = 0;i <= m; ++ i) {
		for (register int j = 0;j <= m; ++ j) {
			ans = MOD(ans + dp[n][i][j]);
		}
	}
	printf("%lld\n",ans);

	return 0;
}
posted @ 2022-04-03 08:22  Coros_Trusds  阅读(34)  评论(0编辑  收藏  举报