[AHOI2009]中国象棋

题目描述
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入格式:
一行包含两个整数N,M,之间由一个空格隔开。
输出格式:
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。

数据范围
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6

显然,同一行里我们最多放2个棋子。
限制我们放棋子的条件是纵列。
我的初步想法是,状压把当前行表示出来,记录前面的列放了棋子数,合法转移。
先不说这样一定会T。。代码实现就很难。要用到三进制状压DP。

void init()//预处理三进制状态每一位的值  
{  
    state[0]=1;  
    for(int i=1;i<=10;i++) state[i]=state[i-1]*3;  
    for(int i=0;i<=state[10];i++){  
        int tmp=i;  
        for(int j=0;j<=10;j++){  
            vis[i][j]=tmp%3;  
            tmp/=3;  
        }  
    }  

这样就很麻烦。。而且n,m是100。根本就没法做。。

然而对于任何一个状态,我们并不关心他的具体内容,只关心方案数。我们可不可以处理出转移的方案数,而不表示具体状态?
综上:我们需要一种状态,既可以体现出纵列的约束,又不表示具体状态使得转移方便。
所以f【i】【j】【k】表示前i行,有j列放了一个棋子,有k列放了2个棋子。
显然,初始状态f【0】【0】【0】。
对于每一行,我们的操作有:
1.不放 直接转移
2.放一个在无棋子的列。(有n列状态数则*n)
3.放在一个有棋子的列。(有n列状态数*n)
4.放2个,都在无棋子的列(C n 2)
5.放2个,都在有1个棋子的列(C n 2)
6.放2个,一个在无棋子的列,一个在有棋子的列(乘法原理 n*m)。

然后转移最后求一下 Σf【n】【j】【k】就好了。

#include<bits/stdc++.h>
using namespace std;

#define ll long long
const ll mod=9999973;
const ll MAXN=105;

ll n,m;

inline ll CX2(int X){
    return X*(X-1)/2;
}

ll dp[MAXN][MAXN][MAXN];//f[i][j][k]表示 前i行,有j列放了1个,有k列放了2个。 
//j+k+空=m 

int main(){
    memset(dp,0,sizeof(dp));
    scanf("%lld%lld",&n,&m);
    dp[0][0][0]=1;
    for(int i=0;i<n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k+j<=m;k++){
                if(dp[i][j][k]){//可以略省时间 
                    (dp[i+1][j][k]+=dp[i][j][k])%=mod;//不放
                    if(j+k+1<=m*2&&m-j-k>0)
                    (dp[i+1][j+1][k]+=(dp[i][j][k]*(m-j-k)))%=mod;//放一个放在空处 

                    if(j+k+1<=m*2&&j>=1)
                    (dp[i+1][j-1][k+1]+=(dp[i][j][k]*j))%=mod;//放一个在1处

                    if(j+k+2<=m*2&&m-j-k>1)
                    (dp[i+1][j+2][k]+=(dp[i][j][k]*CX2(m-j-k)))%=mod;//2个都在0处放且不是一个地方 

                    if(j+k+2<=m*2&&j>=2)
                    (dp[i+1][j-2][k+2]+=(dp[i][j][k]*CX2(j)))%=mod;//都在1处放 

                    if(j+k+2<=m*2&&m-j-k>0&&j>=1)
                    (dp[i+1][j][k+1]+=(dp[i][j][k]*((m-j-k)*j)))%=mod;//一个放0,一个放1.  
                }   
            }
    ll ans=0;
    for(int j=0;j<=m;j++)
        for(int k=0;k+j<=m;k++){
            (ans+=dp[n][j][k])%=mod;
        }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值