原题: https://www.luogu.org/problemnew/show/P2051
题意: n*m的棋盘,放任意数量的炮,要求没有一个跑会被吃,求放法的数量。
解析:
直接爆搜100*100是不行的,只能dp了。没想到这个dp其实挺难的,要先想好各种放法之间的关系。
对于一种放法分析,交换其任意两列或两行会得到另一种合法的放法。也就是说任意两列的位置关系不重要。
而在行不冲突(不超过2个)的情况下,列的状态只有3种:0、1、2。那么dp可以开两维(另一维用m减去前两维)存这三种状态的列数量。
怎么样保证行不冲突呢? 我们一行一行枚举就行了,一行一行做,放0、1或2个。就永远不会超过2个了。
dp[行下标 i][0数量 j][1数量 k]
表示到i行为止,j列0个棋子,k列1个棋子的放法。
转移方程:
- 不放:
- 放一个:
- 在0列:
- 在1列:
- 放两个:
- 在0列:
- 在1列:
- 0、1各一个:
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[2][109][109];//滚动数组
const LL mod =9999973;
int main(){
int n,m;cin>>n>>m;
int f=0;
dp[!f][m][0]=1;
for(int i=1;i<=n;i++){
memset(dp[f],0,sizeof dp[f]);
for(int j=0;j<=m;j++){
for(int k=0;j+k<=m;k++){
dp[f][j][k]+=dp[!f][j][k];
if(j)
dp[f][j-1][k+1]+=dp[!f][j][k]*j;
if(k)
dp[f][j][k-1]+=dp[!f][j][k]*k;
if(j>1)
dp[f][j-2][k+2]+=dp[!f][j][k]*j*(j-1)/2;
if(k>1)
dp[f][j][k-2]+=dp[!f][j][k]*k*(k-1)/2;
if(j&&k)
dp[f][j-1][k]+=dp[!f][j][k]*j*k;
}
}
for(int j=0;j<=m;j++){
for(int k=0;j+k<=m;k++){
dp[f][j][k]%=mod;
}
}
f=!f;
}
LL ans=0;
for(int j=0;j<=m;j++){
for(int k=0;j+k<=m;k++){
ans+=dp[!f][j][k];
}
}
printf("%lld\n",ans%mod);
}