题目:?1287: B304
参考:?WUST OJ 1287:B304(DP)——Mitsuha_
题目?
- 有一间教室,里面有
N
行M
列桌子(N
和M
键盘输入) - 每个桌子上最多有一朵?(0朵花或者1朵花),?的总数量不限
- 每行和每列的?的数量都只能为奇数
- 最多有100行100列桌子
- 问题:有
N
行M
列桌子,?的摆放方法有多少种?
分析
感谢参考博客,因为我是直接拿参考博客的代码运行,通过大量数据总结的规律。
实际上,这种题做的少的话,不容易找到规律(不信可以试试?)。
?画表格分析(带下划线的数据是?参考博客代码运行出来的,如果你能算出来,评论区讨论✒️,给你点赞?表示佩服)
N \ M | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 1 | 0 | 1 | 0 | 1 | 0 |
2 | 0 | 2 | 0 | 8 | 0 | 32 |
3 | 1 | 0 | 16 | 0 | 256 | 0 |
4 | 0 | 8 | 0 | 512 | 0 | 32768 |
5 | 1 | 0 | 256 | 0 | 65536 | 0 |
6 | 0 | 32 | 0 | 32768 | 0 | 33554432 |
- 最先想到的应该是对称矩阵,因此代码中,我们只需要初始化右上三角部分即可(注:文末有思考题,看完再思考。)
- 其次,可以发现规律:
N
和M
奇偶性不同的时候,方法种数必为0
。(能手画总结出来)
看完表格可能就发现所有的数都是2n
(n为自然数)的形式,n
的取值情况如?,不考虑上面表格中值为0
的情况,不考虑左下三角的情况:
N \ M | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | |||
2 | 1 | 3 | 5 | |||
3 | 4 | 8 | ||||
4 | 9 | 15 | ||||
5 | 16 | |||||
6 | 25 |
可以看(连猜带蒙)出
第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;
- 代码原创,公开引用不能删除首行注释(作者,版本号,时间等信息);
- 如果有疑问欢迎评论区留言,尽量解答;
- 如果有错误,还望大侠评论区指正。