• CCF201409-5 拼图(30分)


    试题编号: 201409-5
    试题名称: 拼图
    时间限制: 3.0s
    内存限制: 256.0MB
    问题描述:
    问题描述
      给出一个n×m的方格图,现在要用如下L型的积木拼到这个图中,使得方格图正好被拼满,请问总共有多少种拼法。其中,方格图的每一个方格正好能放积木中的一块。积木可以任意旋转。

    输入格式
      输入的第一行包含两个整数n, m,表示方格图的大小。
    输出格式
      输出一行,表示可以放的方案数,由于方案数可能很多,所以请输出方案数除以1,000,000,007的余数。
    样例输入
    6 2
    样例输出
    4
    样例说明
      四种拼法如下图所示:

    评测用例规模与约定
      在评测时将使用10个评测用例对你的程序进行评测。
      评测用例1和2满足:1<=n<=30,m=2。
      评测用例3和4满足:1<=n, m<=6。
      评测用例5满足:1<=n<=100,1<=m<=6。
      评测用例6和7满足:1<=n<=1000,1<=m<=6。
      评测用例8、9和10满足:1<=n<=10^15,1<=m<=7。

    问题链接CCF201409试题

    问题描述:(参见上文)

    问题分析:也许从数学上先推演一下比较好,这有点难。

    看似可以用递归函数来实现,实际做起来没有那么容易。只得了30分,有胜于无而已。

    对于输入的n和m,若n*m不是3的倍数,则肯定无法完全覆盖。同时,如果不是6的倍数则不能完全覆盖。

    n和m哪个更大是不知道的,假设n<=m的情况下进行计算即可,若n>m将n和m交换即可。题意中则相反,是m<=n,实际上是一样的。

    以下给出若各种拼图组合,可以在其基础上找出递归函数关系:

     

      

     


    程序说明:程序有待改进,或者需要新的思路。

    提交后得30分的C++语言程序如下:

    /* CCF201409-5 拼图 */
    
    #include <iostream>
    
    using namespace std;
    
    const long long MOD = 1000000007;
    
    // 模幂计算函数
    inline long long powermod(int x, int y, long long mod)
    {
        long long ans = 1;
    
    
        while(y) {
            if(y & 1) {
                ans *= x;
                ans %= mod;
            }
            x *= x;
            x %= mod;
            y >>= 1;
        }
    
        return ans;
    }
    
    long long puzzle(int n, int m)
    {
        long long b1, b2;
    
        if(n <= 1 || m <= 1)
            return 0;
    
        // 面积不是3的倍数则不可能
        if((n * m) % 3 != 0)
            return 0;
    
        // 让n<=m
        if(n > m) {
            int temp = n;
            n = m;
            m = temp;
        }
    
        if(n == 2) {   // 2 * 3 * k, k = m / 3
            // n=2时,m必须是3的倍数
            if(m % 3 != 0)
                return 0;
            return powermod(2, m / 3, MOD);
        } else if(n == 3) { // 3 * 2 * k, k = m / 2
            // n=3时,m必须是2的倍数
            if(m % 2 != 0)
                return 0;
            return powermod(2, m / 2, MOD);
        } else if(n == 4 && m == 6) { // 4 * 6
            b1 = puzzle(2, m);  // 分为两半
            b2 = puzzle(2, 3);   // 4*6异形数量
    
            return (b1 * b1 + b2) % MOD;
        } else if(n == 4) { // 4 * 3 * 2 * k, k = m / 6 或 4 * 3 * 2 * k + 4 * 3, k = m / 6
            b1 = powermod(puzzle(4, 6), m / 6, MOD);
    
            if(m % 6 == 0) {
                return b1;
            } else if(m % 6 == 3) {
                // 除了组合,还需要考虑排列
                b2 = puzzle(3, 4);
    
                return b1 * b2 * (m / 6 + 1) % MOD;
            } else
                return 0;
        } else if(n == 5 && m == 6) {
            long long b1, b2;
    
            b1 = puzzle(3, m);
            b2 = puzzle(2, m);
    
            return b1 * b2 * 2 % MOD;   // 64
        } else if(n == 5) {
            if(m % 6 != 0)
                return 0;
            return powermod(puzzle(5, 6), m / 6, MOD);
        } else if(n == 6 && m == 6) {
            // 上下分,左右分,4*6异形与2*6拼接,6*6异形(2种)
            b1 = puzzle(3, m);  // 上下分,或左右分
            b2 = puzzle(2, 3);   // 4*6异形数量
    
            return (b1 * b2 * 2 + b2 * puzzle(2, 6) * 4 + 2) % MOD;
        } else if(n == 6) {
            // 6*6不正确,这个计算就没有意义了
            return 0;
        } else {
            // 其他情况:不考虑
            return 0;
        }
    }
    
    int main()
    {
        int n, m;
        long long ans;
    
        cin >> n >> m;
    
        ans = puzzle(n, m);
    
        cout << ans << endl;
    
        return 0;
    }

    改进:上述程序提交后只得30分,所以进行了进一步的分析与改进:

    1.使得方格图正好被拼满,需要m*n是6的倍数,因为积木块面积为3。程序中35-37行代码改为:

        // 方格图被完全拼满,面积不是6的倍数则不可能
        if((n * m) % 6 != 0)
            return 0;

    2.根据题意,m最大为7,即只需要考虑m=2,3,4,5,6,7的情况。这也是得100分的希望所在。

    所以,程序中只需要考虑n=2,3,4,5,6,7的情况(程序中,与题意相比,n和m互换)。

    3.程序中增加n=7的代码,需要考虑6*7和7*m的情况。对于6*7的方格图,可以由6*2和6*5的方格图组成,也可以由6*3和6*4的方格图组成。

    改进后提交得30分的C++语言程序如下:

    /* CCF201409-5 拼图 */
    
    #include <iostream>
    
    using namespace std;
    
    const long long MOD = 1000000007;
    
    // 模幂计算函数
    inline long long powermod(int x, int y, long long mod)
    {
        long long ans = 1;
    
    
        while(y) {
            if(y & 1) {
                ans *= x;
                ans %= mod;
            }
            x *= x;
            x %= mod;
            y >>= 1;
        }
    
        return ans;
    }
    
    long long puzzle(int n, int m)
    {
        long long b1, b2;
    
        if(n <= 1 || m <= 1)
            return 0;
    
        // 方格图被完全拼满,面积不是6的倍数则不可能
        if((n * m) % 6 != 0)
            return 0;
    
        // 让n<=m
        if(n > m) {
            int temp = n;
            n = m;
            m = temp;
        }
    
        if(n == 2) {   // 2 * 3 * k, k = m / 3
            // n=2时,m必须是3的倍数
            if(m % 3 != 0)
                return 0;
            return powermod(2, m / 3, MOD);
        } else if(n == 3) { // 3 * 2 * k, k = m / 2
            // n=3时,m必须是2的倍数
            if(m % 2 != 0)
                return 0;
            return powermod(2, m / 2, MOD);
        } else if(n == 4 && m == 6) { // 4 * 6
            b1 = puzzle(2, m);  // 分为两半
            b2 = puzzle(2, 3);   // 4*6异形数量
    
            return (b1 * b1 + b2) % MOD;
        } else if(n == 4) { // 4 * 3 * 2 * k, k = m / 6 或 4 * 3 * 2 * k + 4 * 3, k = m / 6
            b1 = powermod(puzzle(4, 6), m / 6, MOD);
    
            if(m % 6 == 0) {
                return b1;
            } else if(m % 6 == 3) {
                // 除了组合,还需要考虑排列
                b2 = puzzle(3, 4);
    
                return b1 * b2 * (m / 6 + 1) % MOD;
            } else
                return 0;
        } else if(n == 5 && m == 6) {
            long long b1, b2;
    
            b1 = puzzle(3, m);
            b2 = puzzle(2, m);
    
            return b1 * b2 * 2 % MOD;   // 64
        } else if(n == 5) {
            if(m % 6 != 0)
                return 0;
            return powermod(puzzle(5, 6), m / 6, MOD);
        } else if(n == 6 && m == 6) {
            // 上下分,左右分,4*6异形与2*6拼接,6*6异形(2种)
            b1 = puzzle(3, m);  // 上下分,或左右分
            b2 = puzzle(2, 3);   // 4*6异形数量
    
            return (b1 * b2 * 2 + b2 * puzzle(2, 6) * 4 + 2) % MOD;
        } else if(n == 6 && m == 7) {
            b1 = puzzle(2, 6) * puzzle(5, 6);
            b2 = puzzle(3, 6) * puzzle(4, 6);
            return (b1 + b2) % MOD;
        } else if(n == 6) {
            // 6*6不正确,这个计算就没有意义了
            return 0;
        } else if(n == 7) {
            return powermod(puzzle(6, 7), m / 6, MOD);
        } else {
            // 其他情况:不考虑
            return 0;
        }
    }
    
    int main()
    {
        int n, m;
        long long ans;
    
        cin >> n >> m;
    
        ans = puzzle(n, m);
    
        cout << ans << endl;
    
        return 0;
    }

    经过改进的程序仍然的30分,增加的有关7*m的代码完全不得分。

    由于6*7的方格图,可以由6*2和6*5的方格图组成,也可以由6*3和6*4的方格图组成,那应该是以下几种方格图的组合数计算有错误:

    6*2

    6*5

    6*3

    6*4

    经过仔细考察和梳理,终于发现4*6至少漏算了一个情况,至少应该如下图所示:



    上述程序的56-50行改为(异形数量*2):

        } else if(n == 4 && m == 6) { // 4 * 6
            b1 = puzzle(2, m);      // 分为两半
            b2 = puzzle(2, 3) * 2;   // 4*6异形数量
    
            return (b1 * b1 + b2) % MOD;

    改进后提交只得20分,分数更少了,怀疑测试数据有BUG。

    下一步只能考虑先改进6*6的情形了。




     











  • 相关阅读:
    北京大学数学分析习题集参考解答03.07难题
    北京大学数学分析习题集参考解答03.06一致连续性
    北京大学数学分析习题集参考解答03.05最大、最小值
    北京大学数学分析习题集参考解答03.03中间值性质03.04初等函数的连续性
    北京大学数学分析习题集参考解答03.02连续函数的运算
    北京大学数学分析习题集参考解答03.01连续与间断
    [Oracle工程师手记] 利用 DBMS_SQLTUNE.report_sql_monitor 生成 SQL 语句的监控信息
    [Oracle工程师手记] Data Guard 环境中,查找最近发生的与 Data Guard 相关的错误的方法
    [Oracle 工程师手记] Windows 环境下,获取与 oracle 相关 registry 的小技巧
    [Oracle工程师手记]从RAC环境备份后向新环境(文件系统)恢复的试验
  • 原文地址:https://www.cnblogs.com/tigerisland/p/7564110.html
Copyright © 2020-2023  润新知