题目链接
题目描述
在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。
(在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。)
输入输出格式
输入格式:
一行包含两个整数N,M,之间由一个空格隔开。
输出格式:
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
样例
输入样例 | 输出样例 |
---|---|
1 3 | 7 |
思路
1. 30分做法
dfs,复杂度玄学
没有代码
2.50分做法
分析题目
当满足题意下,每行每列有且最多只有两个炮
注意题目保证行或列小于等于8
考虑dp 按行或列转移(假设按行转移,即n<=8)
如果知道了考虑到当前行时,每列已经放了的棋子个数
那么当前行放棋子的方案就能够确定(每行最多放两个)
那么如果会状压的同学应该已经会了 (逃
定义dp[n][4m]表示当考虑到第n行时,之前所放的棋子状态为22m时的方案数
通过两位二进制表示一列的状态 00代表没有放棋子 01代表放了一个棋子 11代表不能再放棋子
然后转移就可以了
没有代码
3.满分做法
考虑dp转移过程
- 不选
- 选00->01
- 选01->11
- 选00,00->01,01
- 选00,01->01,11
- 选01,01->11,11
如果你发现,转移并不用知道每一列究竟放了几个棋子的话...
定义dp[n][i][j]表示当考虑到第n行时,没有放棋子的有i列,放了一个棋子的有j列
定义C(x,y)为在x中取y个的方案数
那么上面的转移过程表示出来就是
- C(i+j,0)
- C(i,1)
- C(j,1)
- C(i,2)
- C(i,1)*C(j,1)
- C(j,2)
假设现在取了 00,01
那么就有 dp[n+1][i-1][j-1]=dp[n][i][j]*C(i,1)*C(j,1)
有代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rint register int
#define ci const int&
#define ll long long
using namespace std;
const int mod=9999973;
ll dp[101][102][102];
int n,m;
ll ans=0;
int main(){
cin>>n>>m;
//初始化,在第0行,放了1个的列为0(即01为0),放了0个的列为m(即00为m)的情况数有一种
dp[0][0][m]=1;
for(rint i=0;i<n;i++)
for(rint j=0;j<=m;j++)
for(rint k=0;k<=m-j;k++)if(dp[i][j][k]){//如果状态可到达
if(k){//当00数>=1时
if(j)dp[i+1][j][k-1]+=dp[i][j][k]*k*j,dp[i+1][j][k-1]%=mod;//当01数>=1时,选00,01放置
dp[i+1][j+1][k-1]+=dp[i][j][k]*k;dp[i+1][j+1][k-1]%=mod;//选00放置
if(k>1)dp[i+1][j+2][k-2]+=dp[i][j][k]*k*(k-1)/2,dp[i+1][j+2][k-2]%=mod;//当00数>=2时,选00,00放置
}
if(j){//当01数>=1时
dp[i+1][j-1][k]+=dp[i][j][k]*j;dp[i+1][j-1][k]%=mod;//选01放置
if(j>1)dp[i+1][j-2][k]+=dp[i][j][k]*j*(j-1)/2,dp[i+1][j-2][k]%=mod;//当01数>=2时,选01,01放置
}
dp[i+1][j][k]+=dp[i][j][k];dp[i+1][j][k]%=mod;//不放
}
for(rint j=0;j<=m;j++)
for(rint k=0;k<=m-j;k++)if(dp[n][j][k])(ans+=dp[n][j][k])%=mod;//统计
cout<<ans;
//dp数组用long long,中间存在乘法,极限情况下会爆int,亲身试验....
}