题面
题意
给出一个 m ∗ n m*n m∗n的棋盘,问在其中放 2 ∗ m 2*m 2∗m个炮,使他们两两不会攻击的方案数是多少。
做法
当n,m都小于等于2000时,直接记录有几列放了一个棋子,几列没放棋子,即可逐行转移。
当大于2000时,可以利用
n
−
m
<
=
10
n-m<=10
n−m<=10的性质,考虑最后一列放几个棋子,分以下三种方案进行讨论:
1.最后一列放两个棋子,考虑这两个棋子所在的两行的另外两颗棋子a,b:
如果a,b在同一列,则
f
(
m
,
n
)
+
=
f
(
m
−
2
,
n
−
2
)
∗
(
m
∗
(
m
−
1
)
/
2
)
∗
(
n
−
1
)
f(m,n)+=f(m-2,n-2)*(m*(m-1)/2)*(n-1)
f(m,n)+=f(m−2,n−2)∗(m∗(m−1)/2)∗(n−1)
反之,则可以合并这两行
f
(
m
,
n
)
+
=
f
(
m
−
1
,
n
−
1
)
∗
(
m
∗
(
m
−
1
)
/
2
)
∗
2
f(m,n)+=f(m-1,n-1)*(m*(m-1)/2)*2
f(m,n)+=f(m−1,n−1)∗(m∗(m−1)/2)∗2
2.最后一列只放一个棋子,则这个棋子所在行的另一个棋子的所在列最多放1个棋子,因此只要减去那一列放两个棋子的方案数即可直接转移。
3.最后一列不放棋子,则
f
(
m
,
n
)
+
=
f
(
m
,
n
−
1
)
f(m,n)+=f(m,n-1)
f(m,n)+=f(m,n−1)
代码
#include<bits/stdc++.h>
#define ll long long
#define P pair<ll,ll>
#define mp make_pair
#define fi first
#define se second
#define N 100100
#define M 998244353
using namespace std;
ll m,n,dp[N][15];
inline ll C2(ll u){return u*(u-1)/2%M;}
ll dfs(ll u,ll v);
inline ll calc(ll u,ll v){return C2(u)*((2*dfs(u-1,v)+(u+v-1)*dfs(u-2,v)%M)%M)%M;}
ll dfs(ll u,ll v)
{
if(!u&&!v) return 1;
if(u<0 || v<0) return 0;
if(dp[u][v]!=-1) return dp[u][v];
ll res=0;
res+=calc(u,v);
if(v)
{
res+=u*(u+v-1)%M*((dfs(u-1,v)+M-calc(u-1,v))%M)%M;
res+=dfs(u,v-1);
}
return dp[u][v]=res%M;
}
namespace solve
{
ll jl[2010][2010];
ll dfs(ll u,ll v)
{
if(u*2+v+m*2==n*2) return 1;
if(jl[u][v]!=-1) return jl[u][v];
ll res=0;
if(u>1) res+=dfs(u-2,v+2)*u*(u-1)/2%M;
if(u&&v) res+=dfs(u-1,v)*u*v%M;
if(v>1) res+=dfs(u,v-2)*v*(v-1)/2%M;
return jl[u][v]=res%M;
}
void work()
{
memset(jl,-1,sizeof(jl));
cout<<dfs(n,0);
}
}
int main()
{
memset(dp,-1,sizeof(dp));
ll i,j;
cin>>m>>n;
if(m<=2000&&n<=2000)
{
solve::work();
return 0;
}
cout<<dfs(m,n-m);
}