AHOI2009 中国象棋

题目链接

题目描述

在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。
(在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。)

输入输出格式

输入格式:

一行包含两个整数N,M,之间由一个空格隔开。

输出格式:

总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。

样例

输入样例 输出样例
1 3 7

思路

1. 30分做法

dfs,复杂度玄学

没有代码

2.50分做法

分析题目
当满足题意下,每行每列有且最多只有两个炮
注意题目保证行或列小于等于8
考虑dp 按行或列转移(假设按行转移,即n<=8)
如果知道了考虑到当前行时,每列已经放了的棋子个数
那么当前行放棋子的方案就能够确定(每行最多放两个)
那么如果会状压的同学应该已经会了 (逃
定义dp[n][4m]表示当考虑到第n行时,之前所放的棋子状态为22m时的方案数
通过两位二进制表示一列的状态 00代表没有放棋子 01代表放了一个棋子 11代表不能再放棋子
然后转移就可以了

没有代码

3.满分做法

考虑dp转移过程

  • 不选
  • 选00->01
  • 选01->11
  • 选00,00->01,01
  • 选00,01->01,11
  • 选01,01->11,11

如果你发现,转移并不用知道每一列究竟放了几个棋子的话...
定义dp[n][i][j]表示当考虑到第n行时,没有放棋子的有i列,放了一个棋子的有j列

定义C(x,y)为在x中取y个的方案数
那么上面的转移过程表示出来就是

  • C(i+j,0)
  • C(i,1)
  • C(j,1)
  • C(i,2)
  • C(i,1)*C(j,1)
  • C(j,2)

假设现在取了 00,01
那么就有 dp[n+1][i-1][j-1]=dp[n][i][j]*C(i,1)*C(j,1)

有代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rint register int
#define ci const int&
#define ll long long
using namespace std;

const int mod=9999973;
ll dp[101][102][102];
int n,m;
ll ans=0;

int main(){
    cin>>n>>m;
    //初始化,在第0行,放了1个的列为0(即01为0),放了0个的列为m(即00为m)的情况数有一种 
    dp[0][0][m]=1;
    for(rint i=0;i<n;i++)
        for(rint j=0;j<=m;j++)
            for(rint k=0;k<=m-j;k++)if(dp[i][j][k]){//如果状态可到达 
                if(k){//当00数>=1时 
                    if(j)dp[i+1][j][k-1]+=dp[i][j][k]*k*j,dp[i+1][j][k-1]%=mod;//当01数>=1时,选00,01放置 
                    dp[i+1][j+1][k-1]+=dp[i][j][k]*k;dp[i+1][j+1][k-1]%=mod;//选00放置 
                    if(k>1)dp[i+1][j+2][k-2]+=dp[i][j][k]*k*(k-1)/2,dp[i+1][j+2][k-2]%=mod;//当00数>=2时,选00,00放置 
                }
                if(j){//当01数>=1时 
                    dp[i+1][j-1][k]+=dp[i][j][k]*j;dp[i+1][j-1][k]%=mod;//选01放置 
                    if(j>1)dp[i+1][j-2][k]+=dp[i][j][k]*j*(j-1)/2,dp[i+1][j-2][k]%=mod;//当01数>=2时,选01,01放置 
                }
                dp[i+1][j][k]+=dp[i][j][k];dp[i+1][j][k]%=mod;//不放 
            }
    for(rint j=0;j<=m;j++)
        for(rint k=0;k<=m-j;k++)if(dp[n][j][k])(ans+=dp[n][j][k])%=mod;//统计 
    cout<<ans;
    //dp数组用long long,中间存在乘法,极限情况下会爆int,亲身试验.... 
}
posted @ 2018-07-26 01:36  ullio  阅读(114)  评论(0编辑  收藏  举报