題目來源:
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(波利亞定理)做的速度也比較快,就不貼代碼了。