WUSTOJ 1287: B304(Java:355ms,C:8ms)

    xiaoxiao2022-07-13  164

    题目:?1287: B304
    参考:?WUST OJ 1287:B304(DP)——Mitsuha_
    题目?
    有一间教室,里面有N行M列桌子(N和M键盘输入)每个桌子上最多有一朵?(0朵花或者1朵花),?的总数量不限每行和每列的?的数量都只能为奇数最多有100行100列桌子问题:有N行M列桌子,?的摆放方法有多少种?
    分析

    感谢参考博客,因为我是直接拿参考博客的代码运行,通过大量数据总结的规律。 实际上,这种题做的少的话,不容易找到规律(不信可以试试?)。

    ?画表格分析(带下划线的数据是?参考博客代码运行出来的,如果你能算出来,评论区讨论✒️,给你点赞?表示佩服)

    N \ M1234561101010202080323101602560408051203276851025606553606032032768033554432 最先想到的应该是对称矩阵,因此代码中,我们只需要初始化右上三角部分即可(注:文末有思考题,看完再思考。)其次,可以发现规律:N和M奇偶性不同的时候,方法种数必为0。(能手画总结出来)

    看完表格可能就发现所有的数都是2n(n为自然数)的形式,n的取值情况如?,不考虑上面表格中值为0的情况,不考虑左下三角的情况:

    N \ M123456100021353484915516625

    可以看(连猜带蒙)出

    1行的公差d1为 2 * (1 - 1)2行的公差d2为 2 * (2 - 1)3行的公差d3为 2 * (3 - 1),待验证 第4行的公差d4为 2 * (4 - 1),待验证 ... 第i行的公差di为 2 * (i - 1) ...

    递推公式:di = 2 * (i - 1)

    你会想,要是不同行之间也有规律就好了。你别说还真有,请看对角线数据,注:第1个1是初始化的,不在表格中

    0 = 1 + 2 * (1 - 1) - 1 1 = 0 + 2 * (2 - 1) - 1 4 = 1 + 2 * (3 - 1) - 1 9 = 4 + 2 * (4 - 1) - 1 16 = 9 + 2 * (5 - 1) - 1 25 = 16 + 2 * (6 - 1) - 1 由于di = 2 * (i - 1),故 0 = 1 + d1 - 1 1 = 0 + d2 - 1 4 = 1 + d3 - 1 9 = 4 + d4 - 1 16 = 9 + d5 - 1 25 = 16 + d6 - 1

    递推公式:an = an-1 + dn - 1 递推公式都有了,现在代码写起来简单了吧!

    Java
    /** * Time 355ms * @author wowpH * @version 2.2 * @date 2019年5月23日下午6:56:10 * Environment: Windows 10 * IDE Version: Eclipse 2019-3 * JDK Version: JDK1.8.0_112 */ import java.util.Scanner; public class Main { private static final int MOD = 1000000007; // 取模 private static final int MAX_NM = 101; // 桌子最多100行100列 private int[][] ways; // n行m列桌子有ways[n][m]种方法,下标从1开始 public Main() { init(); // 初始化 int m, n; Scanner sc = new Scanner(System.in); while (sc.hasNext()) { n = sc.nextInt(); //行数 m = sc.nextInt(); //列数 // 只初始化右上三角部分,故需要判断 if (n < m) { System.out.println(ways[n][m]); } else { System.out.println(ways[m][n]); } } sc.close(); } private void init() { ways = new int[MAX_NM][MAX_NM]; // 下标从1开始 int diagonal = 1; // 对角线方法种数的指数(注:初始化为1,见分析) int d; // 公差,第k+2列与第k列的方法种数的指数的差 // 计算第i行的方法种数 for (int i = 1; i < MAX_NM; i++) { d = 2 * (i - 1); // 第i行桌子的公差 diagonal += d - 1; // ways[i][i]的指数等于左上角位置的指数加(d-1) ways[i][i] = 1; // 初始i行i列桌子的方法数为1 // 计算i行i列桌子的方法种数 for (int j = 0; j < diagonal; j++) { ways[i][i] = (ways[i][i] << 1) % MOD; } // 计算第i行第j列的方法种数 for (int j = i + 2; j < MAX_NM; j += 2) { // 初始第j列的方法种数为第j-2列的方法种数 ways[i][j] = ways[i][j - 2]; // 第j列是第j-2列的【2的d次方】倍,左移d次 for (int k = 0; k < d; k++) { ways[i][j] = (ways[i][j] << 1) % MOD; } } } } public static void main(String[] args) { new Main(); } }
    C(注释见Java版)
    /** * Time 8ms * @author wowpH * @version 1.1 * @date 2019年5月24日17:24:20 */ #include<stdio.h> #define MAX_NM 101 #define MOD 1000000007 int ways[MAX_NM][MAX_NM]; void init() { int diagonal = 1; int d; for (int i = 1; i < MAX_NM; i++) { d = 2 * (i - 1); diagonal += d - 1; ways[i][i] = 1; for (int j = 0; j < diagonal; j++) { ways[i][i] = (ways[i][i] << 1) % MOD; } for (int j = i + 2; j < MAX_NM; j += 2) { ways[i][j] = ways[i][j - 2]; for (int k = 0; k < d; k++) { ways[i][j] = (ways[i][j] << 1) % MOD; } } } } int main() { init(); int m, n; while (scanf("%d%d", &n, &m) != EOF) { if (n < m) { printf("%d\n", ways[n][m]); } else { printf("%d\n", ways[m][n]); } } return 0; }

    注释如此详细,应该能看懂吧! 思考题:为什么初始化右上角部分,而不是左下角部分?(大佬跳过) 答:现在假设你的程序已经计算出了第一个数[1][1],因此题目演变成你现在要计算[1][3]还是[3][1]的问题?很明显,计算[1][3]只需要移动1位,而计算[3][1]需要移动12位,其他数据同理,前者块,后者慢。 结论:访问二维数组的时候,尽量将移动距离大的(一维)放在外层循环,移动距离小的(二维)放在内层循环。不仅限于此,其他地方也可以这么思考。

    如有帮助,还望?支持,谢谢!

    备注:第一次发现可以贴表情,所以用的有点多,见谅!


    版权声明:

    转载请于首页注明链接形式的WUSTOJ 1287: B304(Java)——wowpH;代码原创,公开引用不能删除首行注释(作者,版本号,时间等信息);如果有疑问欢迎评论区留言,尽量解答;如果有错误,还望大侠评论区指正。

    最新回复(0)