幻方问题(分析 + c++代码)

题目:

         有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;
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值