P2051 [AHOI2009]中国象棋——DP(我是谁,我在哪,为什么)

象棋,给你棋盘大小,然后放炮(炮的数量不限),不能让炮打到其他的炮,问方案数;

数据n,m<=200;

状态压缩似乎能做,但是我不会;

因为只要状态数,所以不必纠结每种状态的具体情况;

可以想出每行每列最多放两个棋子(我想到了吗?);

所以(为什么啊) 设计f[i][j][k]

表示DP到第i行,一列只有一个棋子的有j个,一列只有两个棋子的有k个;

清晰(模糊)转移方法

好,我们终于来到了第i行,加油;

这位OIer并不打算把棋子放在这一行,用f[i][j][k]直接继承f[i-1][j][k]的方案数;

然而(zhu)队友并不想就这样进入下一行,他要放棋子了!

请大家睁大眼睛,他在纠结

因为他最多只能放两个棋子,每一个棋子的位置都影响着当前的方案数;

他现在想放一个棋子,

两种选择:一,放在一列没有棋子的位置上,这样f[i][j][k]+=f[i][j-1][k]*(m-j+1-k)(乘法法则,有j-1个位置可以选)

二,放在一列只有一个棋子的位置上,这样f[i][j][k]+=f[i-1][j+1][k-1]*(j+1);

放两个棋子

(这个zhu队友想到现在头都大了,但是他离胜利不远了)

一,两个都放在没有棋子的一列,f[i][j][k]+=f[i-1][j-2][k]*(C(m-j-k+2))  (组合数,剩下的列中选两个位置,下面有代码)、

二,两个都放在有一个棋子的列上,f[i][j][k]+=f[i-1][j+2][k-2]*((j+2)*(j+1)/2)  (组合数公式)  (要时常记得,j+k<=m,如果放在j上,k会增加,j会减小)

三,一个放在没有棋子的列上,一个放在有一个棋子的列上,f[i][j][k]+=f[i-1][j][k-1]*j*(m-j-k+1) (j个位置有一个,但是一个放在这列j-1,另一个令j+1)

(队友阵亡,因为他没有判断边界);

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mo=9999973;
const int maxn=120;
ll f[maxn][maxn][maxn];
int n,m;
ll C(ll x) 
{
    return (x*(x-1)/2);
}
ll ans;
int main()
{
    scanf("%d%d",&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);
                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]%=mo;
            }
        }
    }
    for(int i=0;i<=m;i++)
    {
        for(int j=0;j<=m-i;j++)
        {
            ans+=f[n][i][j];
            ans%=mo;
        }
    }
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/WHFF521/p/11534099.html