bzoj1801 [Ahoi2009]chess 中国象棋
1801: [Ahoi2009]chess 中国象棋
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 1868 Solved: 1075
[Submit][Status][Discuss]
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
分析:观察50%的数据,发现有一个数非常小,符合状压dp的条件,于是我们考虑每一行怎么放,用状压dp能过50分.
那么怎么样才能通过全部分呢?状压dp比较耗时的原因是枚举每一行的状态用时太多,我们每次都要考虑每一列放或不放,要考虑2^m次,但是这道题只要求计数啊,所以我们完全不必记录每一行到底怎么放,我们只需要考虑有几行怎么放就可以了,也就是说,我们不必考虑每一行具体的怎么放,我们只需要考虑每一行中有多少列不放,放一个,放两个即可。
那么设f[i][j][k]为前i行中有j列放1个,有k列放2个的方案数,可以发现每一行最多只能放两个,那么这些炮可以不放,放在只放了一个的列里,放在没有放的列里,并考虑一下放几个,状态转移方程就出来了,不过这个方程实在是太复杂,可以看代码理解:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <queue> using namespace std; const int mod = 9999973; int n, m,ans; long long f[110][110][110]; int C(int x) { return x * (x - 1) / 2; } int main() { scanf("%d%d", &n, &m); f[0][0][0] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { for (int k = 0; k <= m; k++) { f[i][j][k] = (f[i][j][k] + f[i - 1][j][k]) % mod; //不放 if (j >= 1) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k] * (m - j + 1 - k) % mod) % mod; //在没有的列上放一个 if (k >= 1) f[i][j][k] = (f[i][j][k] + f[i - 1][j + 1][k - 1] * (j + 1) % mod) % mod; //在只有一个的列上放一个 if (j >= 2) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 2][k] * C(m - j + 2 - k) % mod) % mod; //在没有的列上放二个 if (k >= 2) f[i][j][k] = (f[i][j][k] + f[i - 1][j + 2][k - 2] * C(j + 2) % mod) % mod; //在只有一个的列上放二个 if (k >= 1) f[i][j][k] = (f[i][j][k] + f[i-1][j][k - 1] * j * (m - j - k + 1) % mod ) % mod; //在有一个和没有的列上放二个 } } } for (int i = 0; i <= m; i++) for (int j = 0; j <= m; j++) ans = (ans + f[n][i][j]) % mod; printf("%d\n", ans % mod); //while (1); return 0; }