原题:https://www.luogu.org/problemnew/show/P2051
题解:求在n*m中放炮的方案数,每行每列最多2个。
考虑50分的做法,将每一列的状态压缩,用3进制表示。即f(i,j)表示前i行,状态为j的方案数。讨论当前行放的棋子数。
<1> 当这一行不放时,f(i,j)=f(i-1,j)
<2> 放1个时: f(i,j)=sum{ f(i-1,k) }k为可以转移的状态
<3> 放2个时: f(i,j)=sum{f(i-1,k)} k为可以转移的状态
复杂度为O(n*3^m)
100分做法。之前的做法可以发现,不需要知道每个棋子的位置,如 1 0 0 0,表示在第1个位置放一个棋子,等效于1*4。也就是说,只要知道有多少个放1个的列,多少个只放2个的列,就可以通过数学算出来。
设:f[i][j][k] 为前i行,j列放1个棋子,k列放2个棋子。
考虑转移:
<1>不放 f[i][j][k]+= f[i-1][j][k]
<2>放1个 f[i][j][k]+= f[i-1][j+1][k-1]*(j+1) //放在有1个的列,填入后j--,k++,所以应从j+1,k-1枚举
f[i][j][k]+= f[i-1][j-1][k]*(m-(j-1)-k)//放在空的行
<3>放2个 f[i][j][k]+= f[i-1][j+2][k-2]*C(j+2,2) //放在有1个的列,C(j+2,2)表示从j+2选2个
f[i][j][k]+= f[i-1][j-2][k]*C(m-(j-2)-k,2)// 放在空行里
f[i][j][k]+= f[i-1][j][k-1]*j*(m-(k-1)-j) //1个放在有1个列里,1个放在空行里
细节:
1.初值 f[0][0][0]=1
2.对于j+k>m的显然不合理,由于初值不会被转移到,所以无影响。
3.转移的条件很好像,只要让i,j,k>=0就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=110,M=9999973;
int n,m;
ll f[N][N][N];
ll c(int n){
return ((n*(n-1))/2)%M;
}
int main(){
// freopen("test.in","r",stdin);
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-1][j][k];f[i][j][k]%=M;
// 放 1个
if(k-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k-1]*(j+1))%M;
if(j-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k]*(m-(j-1)-k))%M;
//放 2个
if(k-2>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j+2][k-2]*c(j+2))%M;
if(j-2>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j-2][k]*c(m-(j-2)-k))%M;
if(k-1>=0) f[i][j][k]=(f[i][j][k]+f[i-1][j][k-1]*j*(m-(k-1)-j))%M;
}
ll ans=0;
for(int j=0;j<=m;j++){
for(int k=0;k<=m;k++)
ans=(ans+f[n][j][k])%M;
}
printf("%lld\n",ans);
return 0;
}