Luogu P2051 [AHOI2009]中国象棋

不得不佩服这道题的玄妙

首先我们可以发现一个显然的性质:任意一行或任意一列至多只有两个炮

然后就有一种50分的做法:把每一行的情况三进制状压,然后状压DP即可

转移从上面找出所有有0个炮,1个炮,2个炮的列然后枚举新加入的炮的个数

然后我们瞎搞一下可以发现,其实方案总数与炮摆放的位置无关

即我们根本不需要记录炮在第几行第几列,直接记录一下上一行放了0个炮,1个炮,2个炮的列数各有几个然后就可以直接DP了

同样可以优化,因为一行的位置都是m,因此只要知道1个炮的列数和2个炮的列数就可以用m减去他们得到0个炮的列数

因此我们设f[i][j][k]表示当前第i行,有j列有1个炮,有k列有2个炮的方案总数

则可以由f[i][j][k]推得f[i+1]的许多状态

这里的转移有:

  • f[i+1][j][k]+=f[i][j][k] (一个炮也不放)

  • f[i+1][j+1][k]+=f[i][j][k]*(m-i-j)(m-j-k>=1) (在没有炮的列上放一个炮)

  • f[i+1][j-1][k+1]+=f[i][j][k]*j(j>=1) (在只有一个炮的位置上放一个炮)

  • f[i+1][j+2][k]+=f[i][j][k]*(m-j-k)*(m-j-k-1)/2(m-j-k>=2) (在没有炮的位置上放两个炮,这里的方案数要等差数列求和(组合数也可以))

  • f[i+1][j-2][k+2]+=f[i][j][k]*j*(j-1)/2(j>=2)(在有一个炮的位置上放两个炮)

  • f[i+1][j][k+1]+=f[i][j][k]*(m-j-k)*j(m-j-k>=1&&j>=1)(在有一个炮和有没有炮的位置上各放一个炮)

然后就很舒服了,最后求一下所有f[n+1][j][k]的和即可

边界条件:f[1][0][0]=1;

这里由于DP方程只需要由f[i]推得f[i+1],因此可以滚动优化然而这个数据范围还是不需要了,但我仍然滚存了

CODE

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const LL N=105,mod=9999973;
LL f[2][N][N],ans;
int n,m;
inline LL C(LL x)
{
    return (x*(x-1)/2)%mod;
}
inline void inc(LL &x,LL y)
{
    if ((x+=y)>=mod) x-=mod;
}
int main()
{
    register int i,j,k;
    scanf("%d%d",&n,&m);
    f[1][0][0]=1;
    for (i=1;i<=n;++i)
    {
        int now=i&1,nxt=now^1;
        memset(f[nxt],0,sizeof(f[nxt]));
        for (j=0;j<=m;++j)
        for (k=0;j+k<=m;++k)
        if (f[now][j][k])
        {
            inc(f[nxt][j][k],f[now][j][k]);
            if (m-j-k>=1) inc(f[nxt][j+1][k],(f[now][j][k]*(m-j-k))%mod);
            if (j>=1) inc(f[nxt][j-1][k+1],(f[now][j][k]*j)%mod);
            if (m-j-k>=2) inc(f[nxt][j+2][k],(f[now][j][k]*C(m-j-k))%mod);
            if (j>=2) inc(f[nxt][j-2][k+2],(f[now][j][k]*C(j))%mod);
            if (m-j-k>=1&&j>=1) inc(f[nxt][j][k+1],(f[now][j][k]*(m-j-k)*j)%mod);
        }
    }
    for (j=0;j<=m;++j)
    for (k=0;k+j<=m;++k)
    inc(ans,f[(n+1)&1][j][k]);
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/9019132.html
今日推荐