BZOJ 1801: [Ahoi2009]chess 中国象棋 直接DP

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/89401275

title

BZOJ 1801
LUOGU 2051
Description

在N行M列的棋盘上,放若干个炮可以是0个,使得没有任何一个炮可以攻击另一个炮。 请问有多少种放置方法,中国像棋中炮的行走方式大家应该很清楚吧.

Input

一行包含两个整数N,M,中间用空格分开.

Output

输出所有的方案数,由于值比较大,输出其mod 9999973

Sample Input

1 3

Sample Output

7

HINT

除了在3个格子中都放满炮的的情况外,其它的都可以.
100%的数据中N,M不超过100
50%的数据中,N,M至少有一个数不超过8
30%的数据中,N,M均不超过6

Source

Day2

analysis

首先声明:本题正解和 D P 状压DP 没有一丝关系。

  • l z x lzx 学长查找了 l u o g u luogu 上的众多题解后,得出结论:这道题考虑前50,裸的 3 3 进制状压DP
    f [ i ] [ j ] f[i][j] 表示到第i行且前面列的状态为 j j ( 10 10 放了 2 2 个, 01 01 放了 1 1 个, 00 00 没有放)但是 3 3 进制状压DP好像很难,想学的话,建议写一下POJ 1038
  • 正解:我们可以设状态为 f [ i ] [ j ] [ k ] f[i][j][k] 表示到第 i i 行时,有 j j 列有一个炮, k k 列有两个炮。
    这样就有三种情况:
    • i i 行不放炮: f [ i + 1 ] [ j ] [ k ] + = f [ i ] [ j ] [ k ] f[i+1][j][k]+=f[i][j][k]
    • i i 行只放一个炮
      • 放在了原来没有炮的一列 f [ i + 1 ] [ j + 1 ] [ k ] + = f [ i ] [ j ] [ k ] ( m j k ) f[i+1][j+1][k]+=f[i][j][k]*(m-j-k)
      • 放在了原来有一个炮的一列 f [ i + 1 ] [ j 1 ] [ k + 1 ] + = f [ i ] [ j ] [ k ] j f[i+1][j-1][k+1]+=f[i][j][k]*j
    • i i 行放两个炮
      • 都放在了原来没有炮的一列
        f [ i + 1 ] [ j + 2 ] [ k ] + = f [ i ] [ j ] [ k ] ( m j k ) ( m j k ) / 2 f[i+1][j+2][k]+=f[i][j][k]*(m-j-k)*(m-j-k)/2
      • 一个放在了原来有一个炮的一列,一个放在了原来没有炮的一列
        f [ i + 1 ] [ j ] [ k + 1 ] + = f [ i ] [ j ] [ k ] j ( m j k ) f[i+1][j][k+1]+=f[i][j][k]*j*(m-j-k)
      • 都放在了原来有一个炮的一列
        f [ i + 1 ] [ j 2 ] [ k + 2 ] + = f [ i ] [ j ] [ k ] j ( j 1 ) / 2 f[i+1][j-2][k+2]+=f[i][j][k]*j*(j-1)/2
    • 初态: f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0] = 1
    • 末态: f [ n ] [ i ] [ j ] \sum f[n][i][j]
  • 那么我们为什么要这么设状态呢?
  • 首先以行为阶段,根据象棋的规则,在同一行中,至多只能有两个炮,同理:在同一列中,至多只能有两个炮
    • 思考一个可以覆盖整个状态空间的 f f 数组: f [ i ] f[i] 表示到了第 i i
    • 接下来我们想:某列中的炮能否通过位运算求得?
    • 我们能够发现,可能我们目前在第 i i 行,但是在某个 j j 行的 p p 列有一个炮,我们要知道第 i i 行的第 p p 列能否放置炮。
    • 但是 j j 可能与 i i 相差甚远,我们不能直接通过位运算得到,逐行枚举又会耗费大量不必要的时间。
    • 那么我们就干脆将列的状态记录在数组里。
    • 我们想我们其实并不关心到第 i i 行时哪一列有 1 1 个炮,哪一列有两个炮,我们只需要知道到第 i i 行时,有多少列有 1 1 个炮,有多少列有 2 2 个炮,剩下的问题我们能够通过枚举状态解决。

参考资料:Cyxhsa

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=101,mod=9999973;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
ll f[maxn][maxn][maxn];
inline int cnt(int x)
{
	return x*(x-1)/2;
}
int main()
{
	int n,m;
	read(n);read(m);
	f[0][0][0]=1;
	for (int i=0; i<n; ++i)
		for (int j=0; j<=m; ++j)
			for (int k=0; k+j<=m; ++k)
				if (f[i][j][k])
				{
					(f[i+1][j][k]+=f[i][j][k])%=mod;//第i行不放炮
					if (m-j-k>=1)//第i行只放一个炮,并且放在了原来没有炮的一列
						(f[i+1][j+1][k]+=f[i][j][k]*(m-j-k))%=mod;

					if (j>=1)//第i行只放一个炮,并且放在了原来有一个炮的一列
						(f[i+1][j-1][k+1]+=f[i][j][k]*j)%=mod;

					if (m-j-k>=2)//第i行放两个炮,并且都放在了原来没有炮的一列
						(f[i+1][j+2][k]+=f[i][j][k]*cnt(m-j-k))%=mod;

					if (m-j-k>=1 && j>=1)//第i行放两个炮,并且一个放在了原来有一个炮的一列,一个放在了原来没有炮的一列
						(f[i+1][j][k+1]+=f[i][j][k]*j*(m-j-k))%=mod;

					if (j>=2)//第i行放两个炮,并且都放在了原来有一个炮的一列
						(f[i+1][j-2][k+2]+=f[i][j][k]*cnt(j))%=mod;
					f[i][j][k]%=mod;
				}
	ll ans=0;
	for (int i=0; i<=m; ++i)
		for (int j=0; j+i<=m; ++j)
			ans=(ans+f[n][i][j])%mod;
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/89401275