Luogu P2051 [AHOI2009]中国象棋 //DP

hhy给我推的这道题,我不会,我去康了题解.....

题解第一个人说自己20min K一道省选DP,还一次AC

我想着哪个人这么强,往上一翻发现是__stdcall哥哥......也许这就是大佬的世界吧


50pts:

首先可以想到的是状压DP,因为一行一列要最多不超过2个棋子,

所以用3进制表示整个棋盘每列的放了几个,dp[棋盘状态],边界为dp[0]=1

刷表法比填表法好写DP方程,转移时,在0或者1的位置上加1构成新的棋盘状态,

最后统计所有棋盘状态就行了。


100pts:

在50分的DP中我们反思,什么地方浪费了空间?我们全部状压,存了每条列的顺序和状态

但是在这道题中顺序对答案不造成影响,于是我们可以只存在这个状态的列有多少条,而忽略顺序

于是正确的DP状态就来了,dp[摆到x行][有i列摆了1个][有j列摆了2个],

刷表转移,加法原理转移到下一个状态,乘法原理把当前状态和放法相乘,

最后统计答案

刚开始写填表写挂了,然后写刷表,刷表写完了改了填表

刷表法:

#include<bits/stdc++.h>
#define mod 9999973

using namespace std ;

const int MAXN = 110,MAXM = 110;
long long dp[MAXN][MAXM][MAXM],n,m;

int main(){
    cin>>n>>m;
    dp[0][0][0]=1;
    for(int x=0;x<n;x++){
        for(int i=0;i<=m;i++){
            for(int j=0;i+j<=m;j++){
                if(dp[x][i][j]){
                    (dp[x+1][i][j] += dp[x][i][j]) %= mod;
                    if(m-i-j>0)(dp[x+1][i+1][j] += dp[x][i][j] * (m-i-j)) %= mod;
                    if(i>=1)(dp[x+1][i-1][j+1] += dp[x][i][j] * i) %= mod;
                    if(m-i-j-1>0)(dp[x+1][i+2][j] += dp[x][i][j] * (m-i-j) * (m-i-j-1) / 2) %= mod;
                    if(m-i-j>=0 && i>=1)(dp[x+1][i][j+1] += dp[x][i][j] * (m-i-j) * i) %= mod;
                    if(i>=2)(dp[x+1][i-2][j+2] += dp[x][i][j] * (i-1) * i / 2) %= mod;
                }
            }
        }
    }
    long long ans = 0;
    for(int i=0;i<=m;i++){
        for(int j=0;j<=m;j++){
            if(i+j<=m){
                ans += dp[n][i][j];
                ans %= mod;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

填表法:

#include<bits/stdc++.h>
#define mod 9999973

using namespace std ;

const int MAXN = 110,MAXM = 110;
long long dp[MAXN][MAXM][MAXM],n,m;

int main(){
    cin>>n>>m;
    dp[0][0][0]=1;
    for(int x=1;x<=n;x++){
        for(int i=0;i<=m;i++){
            for(int j=0;i+j<=m;j++){
                dp[x][i][j] += dp[x-1][i][j],dp[x][i][j] %= mod;//什么也不做 
                if(i>=1) dp[x][i][j] += dp[x-1][i-1][j] * (m-i-j+1),dp[x][i][j] %= mod;//放一个在一列0上;
                if(j>=1) dp[x][i][j] += dp[x-1][i+1][j-1] * (i+1),dp[x][i][j] %= mod;//放一个在一列1上;
                if(i>=2) dp[x][i][j] += dp[x-1][i-2][j] * (m-i-j+2) * (m-i-j+1) / 2,dp[x][i][j] %= mod;//放两个在2列0上
                if(j>=2) dp[x][i][j] += dp[x-1][i+2][j-2] * (i+2) * (i+1) / 2,dp[x][i][j] %= mod;//放两个在2列1上 
                if(j>=1 && i>=1) dp[x][i][j] += dp[x-1][i][j-1] * (m-i-j+1) * i,dp[x][i][j] %= mod;//放2个,一个在1,一个在2上 
            }
        }
    }
    long long ans = 0;
    for(int i=0;i<=m;i++){
        for(int j=0;i+j<=m;j++){
            ans += dp[n][i][j];
            ans %= mod;
        }
    }
    cout<<ans<<endl;
    return 0;
}

 状态设计是一个巧活,状态是获得答案的最简条件时,这个DP才是最好的。

TAG:SIN_XIII ⑨

 

posted @ 2019-07-24 07:51  SIN_XIII  阅读(154)  评论(0编辑  收藏  举报