1. 程式人生 > >LG_2051_[AHOI2009]中國象棋

LG_2051_[AHOI2009]中國象棋

題目描述

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

輸入輸出格式

輸入格式

一行包含兩個整數N,M,之間由一個空格隔開。

輸出格式

總共的方案數,由於該值可能很大,只需給出方案數模9999973的結果。

樣例

INPUT

1 3

OUTPUT

7

HINT

樣例說明

除了3個格子裡都塞滿了炮以外,其它方案都是可行的,所以一共有22

2-1=7種方案。

資料範圍

100%的資料中N和M均不超過100

50%的資料中N和M至少有一個數不超過8

30%的資料中N和M均不超過6

SOLUTION

dp

本題我一開始想往狀壓上靠,對於每個狀態,我們記一個類似於三進位制的數來維護狀態,但是由於資料範圍十分的迷,100說大也不大,有的題\(10^7\)的都有(當然不是狀壓題),說大也大,100位的數怎麼存怎麼轉移都是不好處理的問題。於是放棄思考直奔題解

題解提示了一個重要的問題:和Brick game那題一樣,本題對兩行之間的狀態轉移並沒有特殊要求,換句話說其實我們並不關心走到最後到底是哪些列沒有炮,哪些列只有一個,哪些列只有兩個,我們只關心到底有多少列沒有炮,有多少列只有一個炮,有多少列有兩個炮。而由於題目給出的限制,同一行最多隻能新增兩個炮,所以可以實現\(O(1)\)

的轉移。

所以我們可以考慮設dp陣列為\(dp[i][j][k]\)表示第\(i\)行,前\(i\)行有\(j\)列含有一個炮,有\(k\)列含有2個炮。

所以轉移可以是這樣:

  1. 本行放兩個,全部放在原來為空不同兩列;
  2. 本行放兩個,一個放在原有一個的某列,一個放在為空的某列;
  3. 本行放兩個,全部放在原有一個的不同兩列;
  4. 本行放一個,放在空列;
  5. 本行放一個,放在原有一個的某列;
  6. 本行不放炮。
    (我程式碼裡也是按此順序轉移的,權當作是註釋看吧)

由此題又可以發現,其實對於很多dp題,它們的優化往往會從優化狀態轉移入手,對於有多個狀態轉移方式的dp,應該考慮清楚這些具體狀態是否必要,可否簡化為一種抽象的狀態,這種思路在

洛谷2018年11月月賽T3咕咕咕的正解中也有體現,是一種比較好的思路。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int N=110;
const int P=9999973;
int n,m;
LL dp[N][N][N];
inline LL C2(LL num) {LL ans=(num)*(num-1)/2;return ans;}
int main(){
    int i,j;
    scanf("%d%d",&n,&m);
    memset(dp,0,sizeof(dp));
    dp[0][0][0]=1; 
    for (i=0;i<n;++i){
        for (j=0;j<=m;++j){
            for (int k=0;(j+k)<=m;++k){
                if (m-j-k>1) dp[i+1][j+2][k]=(dp[i+1][j+2][k]+dp[i][j][k]*C2(m-j-k))%P;
                if ((m-j-k>0)&&(j>0)) dp[i+1][j][k+1]=(dp[i+1][j][k+1]+dp[i][j][k]*(m-j-k)*(j)%P)%P;
                if (j>1) dp[i+1][j-2][k+2]=(dp[i+1][j-2][k+2]+dp[i][j][k]*C2(j)%P)%P;
                if (m-j-k>0) dp[i+1][j+1][k]=(dp[i+1][j+1][k]+dp[i][j][k]*(m-j-k)%P)%P;
                if (j>0) dp[i+1][j-1][k+1]=(dp[i+1][j-1][k+1]+dp[i][j][k]*(j)%P)%P;
                dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%P;
            }
        }
    }
    LL ans=0;
    for (i=0;i<=m;++i)
        for (j=0;(i+j)<=m;++j)
            ans=(ans+dp[n][i][j])%P;
    printf("%lld\n",ans);
    return 0;
}