洛谷P2051 [AHOI2009]中国象棋

来源:互联网 发布:网络教育法律专业 编辑:程序博客网 时间:2024/04/28 21:28

链接

  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 longusing 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 0
原创粉丝点击