题目:
有N阶幻方(魔方),幻方的六个表面的格子分布有激光,上下,前后,左右面的三组激光为三种颜色,激光为直线,可穿透,不考虑反射。现规定,幻方中某一方块是美观的,当且仅当三种颜色同时穿过此方块。同时规定,用‘#’表示此格子激光打开,‘.'为关闭。
输入:阶数
从内部看正面的激光开关情况
从内部看背面的激光开关情况
从内部看左面的激光开关情况
从内部看右面的激光开关情况
从内部看顶面的激光开关情况
从内部看底面的激光开关情况
例子:
3
.##
#.#
.##
##.
#.#
##.
#.#
.#.
###
#.#
.#.
###
###
###
..#
#..
###
###
(3行一组,共6组)
输出:美观的方块数。
分析:
这是一个模拟问题。首先我们要明白题目的空间样貌,根据题目描述的情景,我们可以画出下面的分析图:
【1.幻方激光全部开启(3阶)】
【2.对表面的激光开关对方块影响】
通过分析我们知道,当前表层,后表层中有其一激光打开,路径上的方块就会受影响,也就是说,当前后面激光都关闭,这个方块会缺失这个激光的影响,也可以说此方块不美观。
这个分析给我们一个启示,研究不美观的方块数更加简单。首先我们需要知道,如何设计代码找到某一路径上激光是缺失的,这个问题很好解决:遍历这两组激光分布,当相对的位置格子上数据都为‘.’时,这条路径上的所有方块都不美观。同时注意一个细节,由于分布为幻方内部的视角,那么我们的遍历方式也有变化。你可以绘制一个透视图帮助你理清思路。
通过这个方式,我们可以找到所有缺失的路径,但是我们知道,路径是会有交点的。就3阶来说,我们知道在前后面某一路径上的所有方块,也就是这3个方块为不美观,但是左右面某一路径若与其相交,有一个方块就会重合。
【3.重合情况】
现在就引出解决题目的第二个重点:去重问题。我们也可以想出解决办法,当找到这个路径的坐标时,我们检验前面的组有没有路径与其重合,用路径上的方块总数减去交点个数,就是总不美观方块数的累加值。现在,我们其实已经得到了最基础的解决办法,边遍历边检验,我们测定时间复杂度为N的立方。
但是我对这个方法不满意,我们有更加节约时间更加清晰的算法。
优化:
我们已经知道寻找路径和去重为此题的两个重点,我们将会从这里优化。
最优雅的方式,是这两个问题三组规模同时在一个方法里解决。
1. 创建n*2Xn的数组"wall",分成3组遍历,前后,左右,上下各为一组;前后组找到路径时,在数组wall的右半nxn部分对应位置存‘1’(wall由两个nxn左右拼合),不美观数+n。
2.左右组遍历,找到路径时,在wall的左nxn部分存‘1’,同时以当前行为准遍历,找右nxn同行几个‘1’存在,n减去右半部分‘1’数,是为不美观数的累加值。
3.上下组遍历,找到路径时,设坐标为i,j,在wall中分别找到底第i列,第j+n列,同时遍历这两个列,两个位置有任意一个为‘1’,有point++,n-point,是为不美观的累加值。
4. N的立方减去所有不美观数.
【wall的表示逻辑】
通过上图,我们知道,这个算法的核心思路是构造一个‘展开图’,这个展开图包含缺失路径的位置信息,并将讨论范围减半,同时,在研究左前两面的相交问题时,我们可以就地检测,到上面也是如此。
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
void inisquare(char** p, int n) {
for (int i = 0; i < n; i++) {
p[i] = new char[n];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> p[i][j];
}
}
}
void iniwall(int** wall, int n) {
for (int i = 0; i < n; i++) {
wall[i] = new int[n * 2];
}
for (int i = 0; i < n; i++) {
for (int j = 0 ; j < n * 2; j++) {
wall[i][j] = 0;}}}
void seew(int** p, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n*2; j++) {
cout << p[i][j];
}cout << endl;
}
}
int find(char** p1, char** p2, int n,int**wall,int tag) {
int res = 0;int point = 0;
if(tag==1) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (p2[i][j] == p1[i][n - 1 - j] && p2[i][j] == '.') {
wall[i][j + n] = 1;
point = point + n;
res = point;
}
}
}
}
if (tag == 2) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (p2[i][j] == p1[i][n - 1 - j] && p2[i][j] == '.') {
wall[i][j] = 1;
for (int k = n; k < n * 2; k++) {
if (wall[i][k] == 1) { point++; }
else;
} res = res+(n - point) ;point=0 ;
}
}
}
}
if(tag==3) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (p2[i][j] == p1[n-1-i][j] && p2[i][j] == '.') {
for (int k = 0; k < n; k++) {
if (wall[k][i] == 1 || wall[k][n+j] == 1) { point++; }
else;
} res = res + (n - point); point = 0;
}
}
}
}
return res;
}
int main() {
int n = 0;
cin >> n;
if (!n) {
cout << 0;
return 0;
}
char** p1 = new char* [n];
char** p2 = new char* [n];
char** p3 = new char* [n];
char** p4 = new char* [n];
char** p5 = new char* [n];
char** p6 = new char* [n];
int** wall = new int* [n];
int square = 0;
inisquare(p1, n);
inisquare(p2, n);
inisquare(p3, n);
inisquare(p4, n);
inisquare(p5, n);
inisquare(p6, n);
iniwall(wall, n);
square=square+find(p1, p2, n, wall, 1);
square=square+find(p3, p4, n, wall, 2);
square = square + find(p5, p6, n, wall, 3);
cout << n*n*n-square << endl;
}
注意这里的find函数,很好的贴合了三组不同情况的讨论,一个wall数组使用三次。
由于数据含量较大,我们可以写一个boot,让代码帮我们随机生成一个分布情况,并通过自己手动或其他正确的代码检验答案
boot:
#include <iostream>
#include<stdio.h>
#include <cstdlib>
using namespace std;
int main() {
int n = 0;
cin >>n;
char** p = new char* [n*6];
for (int i = 0; i < n*6; i++) {
p[i] = new char[n];
}
for (int i = 0; i < n * 6; i++) {
for (int j = 0; j < n; j++) {
int num = rand() % 2;
if (num) {
p[i][j] = '#';
}
else p[i][j] = '.';
}
}
for (int i = 0; i < n * 6; i++) {
for (int j = 0; j < n; j++) {
cout << p[i][j];
}cout << endl;
}
}