p2051 [AHOI2009]中國象棋. (bzoj 1801)
題目描述
這次小可可想解決的難題和中國象棋有關,在一個N行M列的棋盤上,讓你放若幹個炮(可以是0個),使得沒有一個炮可以攻擊到另一個炮,請問有多少種放置方法。大家肯定很清楚,在中國象棋中炮的行走方式是:一個炮攻擊到另一個炮,當且僅當它們在同一行或同一列中,且它們之間恰好 有一個棋子。你也來和小可可一起鍛煉一下思維吧!
40pts
考試遇到了這個題,玄學打表得了\(40pts\)
玄學打表吼啊
xjb分析
正解竟然是個\(DP\)? 還有人說是狀壓\(DP\)?哪裏來的狀壓啊!
前置知識
考慮到我們的合法狀態的話,每一行每一列的炮的數量\(\le 2\)
(炮打隔重山?) 顯然 如果一行或者一列有三個炮的話將會不合法.(兩個炮可以互相打啊 qwq)
如何設狀態?
因為每一行每一列的炮的數量\(\leq 2\)
所以我們考慮記數組去存儲有幾列放了一個炮,有幾列放了兩個炮.
我們又需要考慮轉移?
因此設出狀態
\(f[i][j][k]\)代表放了前\(i\)行,有\(j\)列是有一個棋子,有\(k\)列是有2個棋子的合法方案數.
這個時候我們知道全部的列數,又知道一些情況的列數.
所以我們可以求出不放棋子的列數
單步容斥:空的=全部的\(-\)合法的
即空的序列\(=m-j-k\)
確定情況
- 我們可以在當前第\(i\)行不放棋子.
- 我們可以在當前第\(i\)行放一個棋子
- 我們可以在當前第\(i\)行放兩個棋子.
接下來就需要分類討論這些情況.
分類討論
一.不放棋子
我們可以直接繼承上面的狀態.即
\[
f[i][j][k]=f[i-1][j][k]
\]
二.放一個棋子
顯然我們不會選擇放在有兩個棋子的列.
因此存在情況如下
\[\begin{cases}放在有一個棋子的列 f[i][j][k]+=f[i-1][j+1][k-1]\times (j+1) \\\\\\ 放在沒有棋子的列 f[i][j][k]+=f[i-1][j-1][k]\times (m-(j-1)-k)\end{cases}\]
解釋:
放在一個棋子的列
我們在某一個有一個棋子列放置棋子,會使這一列變為有兩個棋子.
即我們要得到\(f[i][j][k]\)
需要在\(j+1\)個有一個棋子的列放置棋子,變為\(j\)個有一個棋子的列而我們又會得到一個新的有兩個棋子的列.因此我們之前必須有\(k-1\)個有兩個棋子的列.
即\(f[i-1][j+1][k-1]\)的狀態可以傳遞給\(f[i][j][k]\)
而我們又可以在\((j+1)\)中的任何一列放置這一個棋子.
因此我們要\(\times (j+1)\)
放在沒有棋子的列
在一個沒有棋子的列放置棋子,我們會得到一個新的有一個棋子的列.
即我們要從\(j-1\)得到\(j\).
而這個時候,我們有兩個棋子的列的數量不會變,所以從\(k\)傳遞即可.
即\(f[i-1][j-1][k]\)的狀態可以傳遞給\(f[i][j][k]\)
又因為我在空列中的任何一列放置這個棋子.
所以要$\times $\((m-(j-1)-k)\)
三.放兩個棋子
這個時候情況會多一個.先請大家自己考慮一下.
這個時候存在情況如下
\[\begin{cases}一個放在有一個棋子的列,另一個放在沒有棋子的列 f[i][j][k]+=f[i-1][j][k-1]\times j \times (m-j-(k-1)) \\\\ 都放在沒有棋子的列 f[i][j][k]+=f[i-1][j-2][k]\times C_{m-(j-2)-k}^{2}\\\\ 都放在有一個棋子的列 f[i][j][k]+=f[i-1][j+2][k-2] \times C_{j+2}^{2}\end{cases}\]
解釋
一個放在有一個棋子的列,一個放在沒有棋子的列
這個時候,我們放置之後 :
一個沒有棋子的列會變成一個有一個棋子的列,而一個有一個棋子的列會變成一個有兩個棋子的列。
此時我們發現,
? 有一個棋子的列的數量不會變,因此第二維依舊為\(j\),
? 又因為我們會新增一個有兩個棋子的列,所以我們需要從\(k-1\)轉移過來.
又因為我們可以在有一個棋子的列隨便放,空列隨便放.
根據乘法原理,需要\(\times j \times (m-j-(k-1))\)
都放在沒有棋子的列
此時我們放置之後
? 會增加兩個新的有一個棋子的列.
因此我們需要從\(j-2\)轉移過來.
而兩個棋子的列的數量並不會改變,所以依舊為\(k\)
又因為在空列中我們隨便放.
根據組合數學,需要\(\times C_{m-(j-2)-k}^{2}\)
都放在有一個棋子的列
我們放置在有一個棋子的列之後:
? 這兩個有一個棋子的列都會變成有兩個子的列.
? 即\(j+2\)變成\(j\),從\(k-2\)變成\(k\)
又因為這些有一個棋子的列我們隨便選擇.
根據組合數學,需要\(\times C_{j+2}^{2}\)
分析完畢
我們需要接下來做的就是判斷邊界,一定要判斷!!(血的教訓!
代碼
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<cstring>
#define mod 9999973
#define int long long
#define R register
using namespace std;
inline void in(int &x)
{
int f=1;x=0;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,m,ans;
int f[108][108][108];
inline int C(int x)
{
return ((x*(x-1))/2)%mod;
}
signed main()
{
in(n),in(m);
f[0][0][0]=1;
for(R int i=1;i<=n;i++)
{
for(R int j=0;j<=m;j++)
{
for(R int k=0;k<=m-j;k++)
{
f[i][j][k]=f[i-1][j][k];
if(k>=1)(f[i][j][k]+=f[i-1][j+1][k-1]*(j+1));
if(j>=1)(f[i][j][k]+=f[i-1][j-1][k]*(m-j-k+1));
if(k>=2)(f[i][j][k]+=f[i-1][j+2][k-2]*(((j+2)*(j+1))/2));
if(k>=1)(f[i][j][k]+=f[i-1][j][k-1]*j*(m-j-k+1));
if(j>=2)(f[i][j][k]+=f[i-1][j-2][k]*C(m-j-k+2));
f[i][j][k]%=mod;
}
}
}
for(R int i=0;i<=m;i++)
for(R int j=0;j<=m;j++)
(ans+=f[n][i][j])%=mod;
printf("%lld",(ans+mod)%mod);
}
p2051 [AHOI2009]中國象棋. (bzoj 1801)