[AHOI2009]中国象棋 题解

Posted lost_heart_hurts

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[AHOI2009]中国象棋 题解相关的知识,希望对你有一定的参考价值。

Statement

[P2051 AHOI2009]中国象棋 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Solution

显然的性质是,同一行列不能放两个以上的棋子

问题在于如何处理行列的关系,不妨把行拎出来考虑

假设我们正在填写第 \\(i\\) 行,发现我们只需要知道有多少个列没有填,多少个列填了一个,多少个列填了两个(满了)就可以进一步填写了(不必知道具体填写方案)。

\\(f[i][j][k]\\) 表示前 \\(i\\) 行,\\(j\\) 列填了一个棋子,\\(k\\) 列填了两个棋子的方案数

此时,有 \\(m-j-k\\) 列什么也没有填,考虑状态转移

不填 \\(f[i][j][k]=f[i-1][j][k]\\)

填 1 个

\\[f[i][j][k]=\\left\\{ \\begin{array}{l} f[i-1][j+1][k-1]\\times (j+1), &填在放了一个的列\\\\ f[i-1][j-1][k] \\times (m-j-k+1),&填在本来就没有的列 \\end{array} \\right. \\]

填 2 个

\\[f[i][j][k]=\\left\\{ \\begin{array}{l} f[i-1][j-2][k]\\times \\tbinom{m-j-k+2}{2},&全部填在本来就没有的列\\\\ f[i-1][j][k-1]\\times j\\times (m-j-k+1),&一个填在没有,一个填在有一个\\\\ f[i-1][j+2][k-2]\\times \\tbinom{j+2}{2},& 全部填在放了一个的列\\\\ \\end{array} \\right. \\]

注意到放了两个的列显然不能再放,一行最多放两个

初状态 \\(f[0][0][0]=1\\)

答案 \\(\\sum_{i=0}^m\\sum_{j=0}^{i+j\\leq\\max(m,2n)}f[n][i][j]\\)

状态数 \\(O(n^3)\\) ,转移 \\(O(1)\\) 时间复杂度 \\(O(n^3)\\)

只用到 \\(f[i],f[i-1]\\) ,空间 \\(O(n^2)\\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 105;
const int mod = 9999973;

int f[N][N][N];
int n,m,ans;

int C(int x){return (x*(x-1)/2)%mod;}

signed main(){
    scanf("%lld%lld",&n,&m);
    f[0][0][0]=1;
    for(int i=1;i<=n;++i)
        for(int j=0;j<=m;++j)
            for(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))%=mod;
                if(j>=1)(f[i][j][k]+=f[i-1][j-1][k]*(m-j-k+1))%=mod;
                if(j>=2)(f[i][j][k]+=f[i-1][j-2][k]*C(m-j-k+2))%=mod;
                if(k>=1)(f[i][j][k]+=f[i-1][j][k-1]*j*(m-j-k+1))%=mod;
                if(k>=2)(f[i][j][k]+=f[i-1][j+2][k-2]*C(j+2))%=mod;
            }
    int ans=0;
    for(int i=0;i<=m;++i)
        for(int j=0;j<=m;++j)
            (ans+=f[n][i][j])%=mod;
    printf("%lld\\n",ans);
    return 0;
}

BZOJ1801:[AHOI2009]中国象棋——题解

http://www.lydsy.com/JudgeOnline/problem.php?id=1801

https://www.luogu.org/problemnew/show/P2051

这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

我思维断掉了,秉承着dp试图自己做出来。

先想到dp方程有一维一定是处理到第几行,剩下的就是前几行的状态。

考虑状压……emm这100的大小状压可承受不起啊。

稍等,一列最多就两个炮啊!

我们完全可以用一种三维进制表示出每一列有多少个炮,这是是50%的做法。

正解延续了这个思想,显然我们只需要记住有多少列有0/1/2炮,经过奇妙的转移就能得到结果。

(然后斜挂不可避……)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll p=9999973;
int n,m;
ll f[101][101][101];
int main(){
    scanf("%d%d",&n,&m);
    f[0][0][0]=1;
    for(int i=0;i<n;i++){
    for(int j=0;j<=m;j++){
        for(int k=0;k+j<=m;k++){
        int l=m-j-k;
        f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%p;
        if(l>0)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*l)%p;
        if(j>0)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%p;
        if(l>1)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*l*(l-1)/2)%p;
        if(l>0&&j>0)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k]*l*j)%p;
        if(j>1)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*j*(j-1)/2)%p;
        }
    }
    }
    ll ans=0;
    for(int j=0;j<=m;j++){
    for(int k=0;k+j<=m;k++){
        ans=(ans+f[n][j][k])%p;
    }
    }
    printf("%lld\\n",ans);
    return 0;
}

+++++++++++++++++++++++++++++++++++++++++++

 +本文作者:luyouqi233。               +

 +欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/+

+++++++++++++++++++++++++++++++++++++++++++

以上是关于[AHOI2009]中国象棋 题解的主要内容,如果未能解决你的问题,请参考以下文章

题解:bzoj1801: [Ahoi2009]chess 中国象棋

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

[AHOI2009]中国象棋

BZOJ 1801 [Ahoi2009]chess 中国象棋

P2051 [AHOI2009]中国象棋[线性DP]

P2023 [AHOI2009]维护序列 题解(线段树