Luogu P2051 [AHOI2009]中國象棋
不得不佩服這道題的玄妙
首先我們可以發現一個顯然的性質:任意一行或任意一列至多只有兩個炮
然後就有一種50分的做法:把每一行的情況三進制狀壓,然後狀壓DP即可
轉移從上面找出所有有0個炮,1個炮,2個炮的列然後枚舉新加入的炮的個數
然後我們瞎搞一下可以發現,其實方案總數與炮擺放的位置無關
即我們根本不需要記錄炮在第幾行第幾列,直接記錄一下上一行放了0個炮,1個炮,2個炮的列數各有幾個然後就可以直接DP了
同樣可以優化,因為一行的位置都是m,因此只要知道1個炮的列數和2個炮的列數就可以用m減去他們得到0個炮的列數
因此我們設f[i][j][k]表示當前第i行,有j列有1個炮,有k列有2個炮的方案總數
則可以由f[i][j][k]推得f[i+1]的許多狀態
這裏的轉移有:
f[i+1][j][k]+=f[i][j][k] (一個炮也不放)
f[i+1][j+1][k]+=f[i][j][k]*(m-i-j)(m-j-k>=1) (在沒有炮的列上放一個炮)
f[i+1][j-1][k+1]+=f[i][j][k]*j(j>=1) (在只有一個炮的位置上放一個炮)
f[i+1][j+2][k]+=f[i][j][k]*(m-j-k)*(m-j-k-1)/2(m-j-k>=2) (在沒有炮的位置上放兩個炮,這裏的方案數要等差數列求和(組合數也可以))
f[i+1][j-2][k+2]+=f[i][j][k]*j*(j-1)/2(j>=2)(在有一個炮的位置上放兩個炮)
f[i+1][j][k+1]+=f[i][j][k]*(m-j-k)*j(m-j-k>=1&&j>=1)(在有一個炮和有沒有炮的位置上各放一個炮)
然後就很舒服了,最後求一下所有f[n+1][j][k]的和即可
邊界條件:f[1][0][0]=1;
這裏由於DP方程只需要由f[i]推得f[i+1],因此可以滾動優化然而這個數據範圍還是不需要了,但我仍然滾存了
CODE
#include<cstdio> #include<cstring> using namespace std; typedef long long LL; const LL N=105,mod=9999973; LL f[2][N][N],ans; int n,m; inline LL C(LL x) { return (x*(x-1)/2)%mod; } inline void inc(LL &x,LL y) { if ((x+=y)>=mod) x-=mod; } int main() { register int i,j,k; scanf("%d%d",&n,&m); f[1][0][0]=1; for (i=1;i<=n;++i) { int now=i&1,nxt=now^1; memset(f[nxt],0,sizeof(f[nxt])); for (j=0;j<=m;++j) for (k=0;j+k<=m;++k) if (f[now][j][k]) { inc(f[nxt][j][k],f[now][j][k]); if (m-j-k>=1) inc(f[nxt][j+1][k],(f[now][j][k]*(m-j-k))%mod); if (j>=1) inc(f[nxt][j-1][k+1],(f[now][j][k]*j)%mod); if (m-j-k>=2) inc(f[nxt][j+2][k],(f[now][j][k]*C(m-j-k))%mod); if (j>=2) inc(f[nxt][j-2][k+2],(f[now][j][k]*C(j))%mod); if (m-j-k>=1&&j>=1) inc(f[nxt][j][k+1],(f[now][j][k]*(m-j-k)*j)%mod); } } for (j=0;j<=m;++j) for (k=0;k+j<=m;++k) inc(ans,f[(n+1)&1][j][k]); printf("%lld",ans); return 0; }
Luogu P2051 [AHOI2009]中國象棋