zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    synchronized 关键字
    synchronized 关键字
    Linux IPC之共享内存
    链表的插入、删除
    链式队列的实现
    链栈的实现
    双色、三色排序问题
    memmove、memcpy、strcpy、memset的实现
    字符串中去掉多余的空格
    华为机试:从一个数组中选取不同的数(均小于10)组成一个最大的三位数
  • 原文地址:https://www.cnblogs.com/cjjsb/p/9019132.html
Copyright © 2011-2022 走看看