洛谷P2051 [AHOI2009]中国象棋

链接

  https://www.luogu.org/problem/show?pid=2051

题解

  很厉害的题目。
  首先这个问题就是在N*M的网格中放若干个棋子使得每一行每一列至多有2个棋子。
  考虑xjb暴力,30%的数据 N,M6 ,那就每一列压成3进制数,f[i][j]表示做了前i列,每一行的状态是j,0表示这行没有棋子,1表示有1个棋子,2表示有2个棋子,这样压成3进制然后dp转移就可以略了。
  观察最大的数据 N,M100 ,显然不可状压。容易发现我们只关心最后的方案数,而不关心棋子的具体分布,因此状态改为f[i][j][k],表示做了前i列,目前有j行是没有棋子的,k行有1个棋子。
  转移有点庞大,直说一个吧,当j>=1时,就是说有j个位置上是0,那么下一行里就可以在对应位置放一个棋子使得这个0变成1,所以转移到f[i+1][j-1][k+1],而你要从j个0中选择任意一个,所以f[i+1][j-1][k+1]+=f[i][j][k]*j,其余的同理。具体代码中有注释。

代码

//动态规划+排列
#include <cstdio>
#include <algorithm>
#define maxn 110
#define mod 9999973ll
#define ll long long
using namespace std;
ll N, M, f[maxn][maxn][maxn], fact[maxn], a[maxn], b[maxn];

void exgcd(ll a, ll b, ll &x, ll &y)
{
    if(!b){x=1,y=0;return;}
    ll xx, yy;
    exgcd(b,a%b,xx,yy);
    x=yy, y=xx-a/b*yy;
}
ll inv(ll a, ll p=mod)
{
    ll x, y;
    exgcd(a,p,x,y);
    return (x+p)%p;
}
void init()
{
    ll i;
    fact[0]=1;
    for(i=1;i<=100;i++)fact[i]=(fact[i-1]*i)%mod;
    for(i=0;i<=100;i++)a[i]=fact[i], b[i]=inv(fact[i]);
}
void dp()
{
    ll i, j, k, t, ans=0;
    f[0][N][0]=1;
    for(i=0;i<=M;i++)
    {
        for(j=0;j<=N;j++)
        {
            for(k=0;j+k<=N;k++)
            {
                t=f[i][j][k]%mod;
                f[i+1][j][k]+=t;
                if(j>=1)f[i+1][j-1][k+1]+=t*j;      //0->1
                if(j>=2)f[i+1][j-2][k+2]+=t*j*(j-1)/2;//0->1,0->1
                if(k>=1)f[i+1][j][k-1]+=t*k;        //1->2
                if(k>=2)f[i+1][j][k-2]+=t*k*(k-1)/2;    //1->2,1->2
                if(j>=1 and k>=1)f[i+1][j-1][k]+=t*j*k; //0->1,1->2
            }
        }
    }
    for(j=0;j<=N;j++)for(k=0;j+k<=N;k++)ans=(ans+f[M][j][k])%mod;
    printf("%lld",ans);
}
int main()
{
    scanf("%lld%lld",&N,&M);
    init();
    dp();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值