题目来源:
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(波利亚定理)做的速度也比较快,就不贴代码了。