编程求幻方(魔方),1-N


题目来源:

2013.11.28 带的程序设计 I 上机(即大一C语言课程)。

题目描述:

有一种正方形的数字排列是一个5×5的数字幻方,即每个1到5的整数在每行每列都出现且出现一次。形式如下:
1 2 3 4 5
2 1 4 5 3
3 4 5 1 2
4 5 2 3 1
5 3 1 2 4
对于一个N×N的幻方,如果我们固定了第一行如下:
1 2 3 4 5...N
我们可以算出符合幻方要求N×N的个数。
输入包括一行,包括一个整数N(2≤N≤7),表述幻方的行列数。
输出也只有一行,包括一个整数,表示符合要求的幻方的个数。
样例输入
5
样例输出
1344

分析:题目已经说了,第一行是固定的,即对N×N的幻方第一行是 1 2 ... N.

            刚看到这一题的想法,大概就是搜索+回溯。首先我们可以简化下问题,题目说第一行固定下来,

            那么现在根据幻方的一些特性,我们也可以假设第一列也固定下来,也为1-N,这个问题记为A

           接下来我们只需要从第二行的第二列开始搜索所有问题的解,最后问题的解个数即为所有的解的

           第2-N行的全排列个A问题解的个数。即(n-1)! 个A的解个数。如图所示。

      

        依照上图,只能找到一个解为下图:

      

   根据上面的分析可知这个问题的解为(3-1)!*1 = 2, 即 n=3时 解的个数为2;

   n = 3 所有解的情形如下:

 1.     2.


代码如下:

#include<stdio.h>
#define M 10

// row[i][1-M]表示在第i行中1-n的位向量,row[i][j]=1表示第i行中数字j已被选择,col同理 
int row[M][M], col[M][M];
double count;//记录解的个数,会超出int范围,因此利用double或者 long long类型,

// x,y 分别代表行列,n为幻方的阶
void dfs(int x, int y, int n)
{
	if(y == n + 1)//一行计算完(即行的列计算完毕)
	{
		if(x == n)// 所有行计算完毕,找到解
		{
			count ++;
			return ;
		}
		else
			dfs(x + 1, 2, n);//计算下一行
	}
	else//继续在第x行搜索余下的列
	{
		int i;
		for(i=1; i <= n;  ++i)//依次遍历1-n个数,选择为选择的数
		{
			if(row[x][i] == 0 && col[y][i] == 0)// 判断i是否已经选择
			{
				row[x][i] = 1;
				col[y][i] = 1; // 选择元素 i 放在第x行第y列
				
				dfs(x, y + 1, n);//计算下一列

				row[x][i] = 0;//回溯
				col[y][i] = 0; 
			}
		} 
	}
} 
int main()
{
	int n, i;
	scanf("%d", &n);
	if(n == 2)
		printf("1\n");
	else 
	{
		// 初始化 第一行,第一列放置 1-n
		for(i = 1; i <= n; i ++)
		{
			row[1][i] = 1; 
			col[1][i] = 1;

			row[i][i] = 1; 
			col[i][i] = 1;
		}
		dfs(2, 2, n);//从第二行第二列开始计算

		// 幻方个数为(n-1)!个count,因为默认第一列为1-n,因此出去第一行固定
		// 的1-n,可以对剩下的n-1行进行全排列,因此有(n-1)!*count 个
		for(i = 1; i < n; i ++)
			count *= i;
		printf("%0.f\n", count);
	}
	return 0;
}

上面的代码在计算 n =7 时 比较慢,原因是搜索时其实可以加一些限制条件。比如行dfs中 x = n -1 时 即可

知道问题有解,第二行第二列个数只能选1或3等等,下面贴个 有个学生的优化后的代码,速度比较快。

代码:

#include <cstdlib> 
#include <cstring>
#include <iostream>
#define rep(i, a, b) for (int i = a; i <= b; i ++)

#define MAXN 10 

using namespace std;

int n, all; 
long long ans1 = 0, ans; 

int col[MAXN], row[MAXN];

void dfs(int x, int y) {
	if (x == n) {
		ans ++;
		return; 
	}
	
	int now = col[y] | row[x]; 
	while (now != all) {
		#define next (((now + 1) | now) - now)
		col[y] |= next, row[x] |= next;
		dfs(y == n ? x + 1 : x, y == n ? 2 : y + 1);
		col[y] ^= next, row[x] ^= next;
		now |= next; 
	}		
}

void prepare() {
	all = (1 << n) - 1;
	memset(row, 0 , sizeof(row)); 
	memset(col, 0 , sizeof(col)); 
	rep(i, 1, n)
		col[i] |= 1 << (i - 1), row[i] |= 1 << (i - 1);
}

long long f(int x) {
	long long ans = 1;
	rep(i, 1, x)
		ans *= i;
	return ans; 
}

void solve() {
	prepare(); 
	row[2] |= 1;col[2] |= 1; 
	dfs(2, 3);
	ans1 = ans; 
	ans = 0;  
	prepare();
	row[2] |= 4;col[2] |= 4;
	dfs(2, 3);
	ans *= (n - 2); 
	ans += ans1; 
}

void special() {
	if (n == 2)
		cout << 1 << endl; 
	if (n == 3)
		cout << 2 << endl; 
	if (n == 4)
		cout << 24 <<endl;
	if (n < 5)
		exit(0);
}


int main() {
	
	cin >> n; 
	special(); 
	solve();   
	cout << ans * f(n - 1); 
	system("pause"); 
	return 0; 	  
}

上面结合位运算,加限制条件等,计算 n =7 时速度快很多。高中搞信息学竞赛的孩子 果然不简单的 大笑

此外还有利用Polya(波利亚定理)做的速度也比较快,就不贴代码了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值