zoukankan      html  css  js  c++  java
  • 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,亲身试验.... 
    }
    
  • 相关阅读:
    (一)Python入门-3序列:04列表-元素删除的3种方式-删除本质是数组元素拷贝
    (一)Python入门-3序列:05列表-元素的访问-元素出现次数统计-成员资格判断
    Java学习笔记_180704_final和static关键字
    Java学习笔记_180702_基本类型和引用类型作为参数传递特性
    Java学习笔记_180702_面向对象编程
    如何手动实现整型数值60的二进制到十六进制的转换
    Java学习笔记_180627_循环控制语句
    兔子生兔子问题(斐波那契数列)
    Java学习笔记_180625_基础语法
    ubuntu和win10双系统,用ubuntu引导win10启动
  • 原文地址:https://www.cnblogs.com/ullio/p/9369323.html
Copyright © 2011-2022 走看看