1. 程式人生 > >p2051 [AHOI2009]中國象棋. (bzoj 1801)

p2051 [AHOI2009]中國象棋. (bzoj 1801)

turn void 存儲 ring 必須 乘法 fin 打表 描述

題目描述

這次小可可想解決的難題和中國象棋有關,在一個N行M列的棋盤上,讓你放若幹個炮(可以是0個),使得沒有一個炮可以攻擊到另一個炮,請問有多少種放置方法。大家肯定很清楚,在中國象棋中炮的行走方式是:一個炮攻擊到另一個炮,當且僅當它們在同一行或同一列中,且它們之間恰好 有一個棋子。你也來和小可可一起鍛煉一下思維吧!

40pts

考試遇到了這個題,玄學打表得了\(40pts\)

玄學打表吼啊

xjb分析

正解竟然是個\(DP\)? 還有人說是狀壓\(DP\)?哪裏來的狀壓啊!

前置知識

考慮到我們的合法狀態的話,每一行每一列的炮的數量\(\le 2\)

(炮打隔重山?) 顯然 如果一行或者一列有三個炮的話將會不合法.(兩個炮可以互相打啊 qwq)

如何設狀態?

因為每一行每一列的炮的數量\(\leq 2\)

所以我們考慮記數組去存儲有幾列放了一個炮,有幾列放了兩個炮.

我們又需要考慮轉移?

因此設出狀態

\(f[i][j][k]\)代表放了前\(i\)行,有\(j\)列是有一個棋子,有\(k\)列是有2個棋子的合法方案數.

這個時候我們知道全部的列數,又知道一些情況的列數.

所以我們可以求出不放棋子的列數

單步容斥:空的=全部的\(-\)合法的

空的序列\(=m-j-k\)

確定情況

  1. 我們可以在當前第\(i\)行不放棋子.
  2. 我們可以在當前第\(i\)行放一個棋子
  3. 我們可以在當前第\(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)