您的位置:首页 > 其它

[AHOI2009]中国象棋

2017-12-04 15:56 302 查看
题目描述

这次小可可想解决的难题和中国象棋有关,在一个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
[j][k])%=mod;
}
printf("%lld\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: