演算法設計與分析基礎 第三章謎題
習題3.1
6.四格拼板 四格拼板是由4個1*1的正方形組成。下面是5種類型的四格拼板:
分別利用以下四格拼板,看看是否有可能在不重疊的情況下完全覆蓋一個8*8的棋盤。
a. 直線拼板 可以,長和寬能被8整除
b. 方形拼板 可以,邊長能被8整除
c. L形拼板 可以,兩個拼板組成2*4拼板,長和寬能被8整除
d. T形拼板 可以,用四個拼板組成4*4拼板,邊長能被8整除
e. Z形拼板 不可能,用Z形拼板無法拼出一個完整的直角
7.一摞假幣 有n摞硬幣,每摞包含n個硬幣,硬幣外觀完全相同。其中一摞硬幣全是假幣,而其他摞全是真幣。每個真幣重10克,每個假幣重11克。你有一個稱重盤,可以對任意數量硬幣精確稱重。
a. 設計一個蠻力演算法識別那摞假幣,並且確定該演算法最壞情況的效率型別
解答:每摞硬幣都取出一枚硬幣,依次稱重,直到稱出11克的硬幣,則該硬幣以及同一摞為假幣。最壞稱重n次,效率型別為θ(n)。
b. 要識別出那摞假幣,至少需要稱重多少次?
解答:稱重一次即可。給每摞硬幣從1到n標號,每一摞取出一些硬幣,取出數量即為標號,對這堆硬幣稱重為x克,若全為真幣重量為y克,x-y即為假幣對應標號。
14.交叉放置的碟子 我們有數量為2n的一排碟子,n黑n白交替放置:黑、白、黑、白……現在要把黑碟子都放在右邊,白碟子都放在左邊,但只允許通過互換相鄰碟子的位置來實現。為該謎題寫個演算法,並確定該演算法需要執行的換位次數。
解答:用1表示黑碟子,0表示白碟子,那麼目前的順序是:1010...1010。結果要求1都放在右邊,0都放在左邊。這個題目看起來與氣泡排序相似。假設目前有6個碟子:101010。使用氣泡排序,第一次迭代,碟子序列變為:010101,交換3次。在進行第二次迭代之前,觀察一下。現在,不僅第一個碟子就位,最後一個也是了,因此第二次迭代只需要對第2到第5個進行排序,巧合的是,碟子[2->5]仍然是10交替出現,不過比上一次少了兩個,可以得到結論:對於2n個碟子,可以使用n次迭代完成,交換的次數分別是:n+(n-1)+...+2+1,即n(n+1)/2。
習題3.2
3.儀器測試 某公司總部大樓有n層,為了測出哪層樓最高,可以用一種儀器從天花板向地板自由落體(當然儀器並不會摔壞)。公司有兩個一模一樣的儀器來進行測試。如果這兩個儀器的其中一個受損,而且無法修復,實驗不得不用剩下的儀器獨立完成。請設計一個具有最佳效率型別的演算法來幫該公司解決這個問題。
解答:此題翻譯有誤,原文是:A firm wants to determine the highest floor of its n-story headquarters from which a gadget can fall without breaking. The firm has two identical gadgets to experiment with. If one of them gets broken, it can not be repaired, and the experiment will have to be completed with the remaining gadget. Design an algorithm in the best efficiency class you can to solve this problem.
題目與兩個雞蛋下落問題類似,第一次到k樓去測,如果摔壞了,只有從1,2,...k-1一個個去測,如果沒有壞,第二次到k+(k-1)樓去測,如果摔壞了,只有從k+1,k+2,...k+(k-2)一個個去測,如果沒有壞,第三次到k+(k-1)+(k-2)樓去測,如果摔壞了,只有從k+(k-1)+1,k+(k-1)+2,...k+(k-1)+(k-3)一個個去測,如果沒有壞,。。。,最後到k+(k-1)+(k-2)+...+1樓去測,則有k+(k-1)+(k-2)+...+1= k(k+1)/2>=n,可得到k,即平均測試次數為k次,效率型別為O( )
10.填字遊戲 “填字”是在美國流行的一種遊戲,它要求遊戲者從一張填滿字母的正方形表中,找出包含在一個給定集合中的所有詞。這些詞可以豎著讀,橫著讀,或者沿45°對角線斜著讀,但這些詞必須是由表格中鄰接的連續單元格構成。遇到表格的邊界時可以環繞,但方向不得改變,也不能折來折去。表格中的同一單元格可以出現在不同的詞中,但在任一詞中,同一單元格不得出現一次以上。為該遊戲設計一個計算機程式。
解答:遍歷正方形表的所有位置,以每個位置為起始,分別向八個方向出發,尋找是否有單詞出現。注意在邊界的位置只需往5個方向搜尋,在頂角的位置只需往三個方向搜尋。
11.海戰遊戲 基於蠻力模式匹配,在計算機上程式設計實現“海戰”遊戲。遊戲的規則如下:遊戲中有兩個對手(在這裡,分別是玩家和計算機),遊戲在兩塊完全相同的棋盤(10*10的方格)上進行的,兩個對手分別在各自的棋盤上放置他們的艦艇,當然對手是看不見的。每一個對手都有5艘艦艇:一艘驅逐艦(2格)、一艘潛艇(3格)、一艘巡洋艦(3格)、一艘戰列艦(4格)和一艘航空母艦(5格)。每艘艦艇都在棋盤上佔據一定數量的格子。每艘艦艇既可以豎著放,也可以橫著放,但任意兩艘艦艇不能互相接觸。遊戲的玩法是雙方輪流“轟炸”對方的艦艇。每次轟炸的結果是擊中還是未擊中都會顯示出來。如果擊中的話,該玩家就可以繼續攻擊,直到擊不中為止。遊戲的目標是趕在對手之前把他所有的艦艇都擊沉。要擊沉一艘潛艇,該艦艇的所有格子都必須命中。
#include <cstdio>
#include <ctime> //rand()%(x) 0~x-1 int
#include <windows.h> //停頓:Sleep();
#include <cstdlib> //清屏:system("cls");
#include <conio.h> //_getch(); char
#include <iostream>
#include <string> //未知 :□; 打中 :◎; 未打中:○ 船:★;
using namespace std;
int rest[3][6], r1, r2; //rest[1]:玩家的海域; rest[2]:電腦的海域 r1:玩家還剩船數; r2:電腦還剩船數
int b1[12][12], b2[12][12]; //0:空海; 1:船隻; 2:打中; 3:邊界 4:未打中 5:沉船
int c1[12][12], c2[12][12]; //c1:玩家海域的顏色 c2:電腦海域顏色
int fx[8][2] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }, { 1, 1 }, { -1, -1 }, { 1, -1 }, { -1, 1 } };
int now[2][2]; //now[左/右][x/y] 游標
string a1[12][12], a2[12][12];
int fd[500][2], head = 0, tail = 0;
void color(int a)//顏色函式
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), a);
}
void gotoxy(int x, int y)//位置函式(整個介面)(行為x 列為y)
{
COORD pos;
pos.X = 2 * (y);
pos.Y = x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void gotoxy1(int x, int y)//位置函式(左邊棋盤)(行為x 列為y)
{
COORD pos;
pos.X = 2 * (y + 5);
pos.Y = x + 1;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void gotoxy2(int x, int y)//位置函式(右邊棋盤)(行為x 列為y)
{
COORD pos;
pos.X = 2 * (y + 18);
pos.Y = x + 1;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
void check2(int x, int y){
int k = 0, kx, ky, f = 1;
for (int i = 0; i <= 3; i++){
if (b2[x + fx[i][0]][y + fx[i][1]] == 2) k = i;
if (b2[x + fx[i][0]][y + fx[i][1]] == 1) f = 0;
}
for (kx = x, ky = y; b2[kx][ky] == 2; kx += fx[k][0], ky += fx[k][1]);
if (b2[kx][ky] == 1) f = 0;
if (f){
int w = 0;
color(12);
for (kx = x, ky = y; b2[kx][ky] == 2; kx += fx[k][0], ky += fx[k][1]){
gotoxy2(kx, ky);
a2[kx][ky] = "※";
c2[kx][ky] = 12;
cout << "※";
w++;
}
for (kx = x + fx[(k + 2) % 4][0], ky = y + fx[(k + 2) % 4][1]; b2[kx][ky] == 2; kx += fx[(k + 2) % 4][0], ky += fx[(k + 2) % 4][1]){
gotoxy2(kx, ky);
a2[kx][ky] = "※";
c2[kx][ky] = 12;
cout << "※";
w++;
}
if (w>0){
rest[2][w]--;
r2--;
if (rest[2][w]>0) color(14); else color(11);
gotoxy2(6 - w, 17); printf("*%d", rest[2][w]);
}
}
}
int move1(){
if (r1*r2 == 0) return(1);
color(5); gotoxy1(14, 4); printf("電腦開炮");
color(13); gotoxy2(14, 4); printf("玩家開炮");
int kx = now[1][0], ky = now[1][1], lastx, lasty, f = 1;
char ch;
gotoxy2(kx, ky); color(11); if (a2[kx][ky] != " ")cout << a2[kx][ky]; else cout << "▂"; gotoxy2(kx, ky);
while (f){
ch = _getch();
if (ch == '1' || ch == 'a'){ kx = now[1][0] + fx[2][0]; ky = now[1][1] + fx[2][1]; }
if (ch == '2' || ch == 's'){ kx = now[1][0] + fx[1][0]; ky = now[1][1] + fx[1][1]; }
if (ch == '3' || ch == 'd'){ kx = now[1][0] + fx[0][0]; ky = now[1][1] + fx[0][1]; }
if (ch == '5' || ch == 'w'){ kx = now[1][0] + fx[3][0]; ky = now[1][1] + fx[3][1]; }
if (kx>0 && kx <= 10 && ky>0 && ky <= 10){
gotoxy2(now[1][0], now[1][1]); color(c2[now[1][0]][now[1][1]]); cout << a2[now[1][0]][now[1][1]];
gotoxy2(kx, ky); color(11); if (a2[kx][ky] != " ")cout << a2[kx][ky]; else cout << "▂"; gotoxy2(kx, ky);
now[1][0] = kx; now[1][1] = ky;
}
if ((ch == '0' || ch == ' ') && b2[kx][ky] <= 1){
if (b2[kx][ky] == 1){ b2[kx][ky] = 2; a2[kx][ky] = "◎"; c2[kx][ky] = 4; }
if (b2[kx][ky] == 0){ b2[kx][ky] = 4; a2[kx][ky] = " "; }
gotoxy2(kx, ky); color(c2[kx][ky]); cout << a2[kx][ky];
f = 0;
check2(kx, ky);
color(7); gotoxy2(12, 4); cout << "("; color(6); cout << ky; color(7); cout << ","; color(2); cout << kx; color(7); cout << ") ";
if (b2[kx][ky] == 2) move1();
}
if (ch == '8' || ch == 'g'){
for (int i = 1; i <= 10; i++) for (int j = 1; j <= 10; j++)
if (b2[i][j] == 1){
gotoxy2(i, j);
color(10);
printf("Ω");
}
char ccc = _getch();
for (; ccc != '8' && ccc != 'g'; ccc = _getch());
for (int i = 1; i <= 10; i++)for (int j = 1; j <= 10; j++){
gotoxy2(i, j);
color(c2[i][j]);
cout << a2[i][j];
}
gotoxy2(kx, ky); color(11); if (a2[kx][ky] != " ")cout << a2[kx][ky]; else cout << "▂"; gotoxy2(kx, ky);
}
if (ch == '4' || ch == 'q') return(0);
}
return(1);
}
int ok(int x, int y){
int nnn = 0;
if (b1[x][y] == 2 || b1[x][y] == 4 || b1[x][y] == 5) return(0);
for (int i = 0; i <= 7; i++){
if (b1[x + fx[i][0]][y + fx[i][1]] == 2) nnn++;
if (b1[x + fx[i][0]][y + fx[i][1]] == 5) return(0);
}
if (nnn>1) return(0);
return(1);
}
void check1(int x, int y) {
int k = 0, kx, ky, f = 1;
for (int i = 0; i <= 3; i++){
if (b1[x + fx[i][0]][y + fx[i][1]] == 2) k = i;
if (b1[x + fx[i][0]][y + fx[i][1]] == 1) f = 0;
}
for (kx = x, ky = y; b1[kx][ky] == 2; kx += fx[k][0], ky += fx[k][1]);
if (b1[kx][ky] == 1) f = 0;
if (f){
int w = 0;
color(12);
for (kx = x, ky = y; b1[kx][ky] == 2; kx += fx[k][0], ky += fx[k][1]){
gotoxy1(kx, ky);
b1[kx][ky] = 5;
a1[kx][ky] = "※";
c1[kx][ky] = 12;
cout << "※";
w++;
}
for (kx = x + fx[(k + 2) % 4][0], ky = y + fx[(k + 2) % 4][1]; b1[kx][ky] == 2; kx += fx[(k + 2) % 4][0], ky += fx[(k + 2) % 4][1]){
gotoxy1(kx, ky);
b1[kx][ky] = 5;
a1[kx][ky] = "※";
c1[kx][ky] = 12;
cout << "※";
w++;
}
if (w>0){
rest[1][w]--;
r1--;
if (rest[1][w]>0) color(14); else color(11);
gotoxy1(6 - w, -5);
printf("%d*", rest[1][w]);
}
}
}
void move2(){
if (r1*r2 == 0) return;
color(13); gotoxy1(14, 4); printf("電腦開炮");
color(5); gotoxy2(14, 4); printf("玩家開炮");
Sleep(750);
int kx = 0, ky = 0, over = 0;
while (tail>head){
head++;
kx = fd[head][0]; ky = fd[head][1];
if (ok(kx, ky)){ over = 1; break; }
}
while (!over){
kx = rand() % (10) + 1;
ky = rand() % (10) + 1;
if (ok(kx, ky)) over = 1;
}
if (b1[kx][ky] == 1){ b1[kx][ky] = 2; a1[kx][ky] = "◎"; c1[kx][ky] = 4; }
if (b1[kx][ky] == 0){ b1[kx][ky] = 4; a1[kx][ky] = " "; }
gotoxy1(kx, ky); color(11); printf("⊕"); Sleep(600);
gotoxy1(kx, ky); color(c1[kx][ky]); cout << a1[kx][ky];
color(7); gotoxy1(12, 4); cout << "("; color(6); cout << ky; color(7); cout << ","; color(2); cout << kx; color(7); cout << ") ";
check1(kx, ky);
if ((b1[kx][ky] == 2 || b1[kx][ky] == 5) && r1*r2>0){
int i = rand() % (4);
for (int ii = 0; ii <= 3; ii++){
int px = kx + fx[i][0], py = ky + fx[i][1];
if (px>0 && px <= 10 && py>0 && py <= 10){
tail++;
fd[tail][0] = px;
fd[tail][1] = py;
}
i = (i + 1) % 4;
}
move2();
}
}
void put(){
int k = 5;
int num[] = { 0, 0, 1, 2, 1, 1 };
while (k--){
for (int i = 1; i <= num[k+1]; i++){
int f1 = 0, f2 = 0;
int dir1, dir2;
dir1 = rand() % (2);
dir2 = rand() % (2);
while (!f1){
f1 = 1;
int lx = rand() % (10) + 1;
int ly = rand() % (10) + 1;
for (int nx = lx - 1; nx <= lx + fx[dir1][0] * k + 1; nx++)
for (int ny = ly - 1; ny <= ly + fx[dir1][1] * k + 1; ny++)
if (b1[nx][ny] == 1){ f1 = 0; break; }
for (int nx = lx; nx <= lx + fx[dir1][0] * k; nx++)
for (int ny = ly; ny <= ly + fx[dir1][1] * k; ny++)
if (b1[nx][ny] == 3){ f1 = 0; break; }
if (f1){
for (int jx = lx, jy = ly; jx <= lx + fx[dir1][0] * k && jy <= ly + fx[dir1][1] * k; jx += fx[dir1][0], jy += fx[dir1][1]){
b1[jx][jy] = 1;
c1[jx][jy] = 15;
color(15);
gotoxy1(jx, jy); printf("□");
}
}
}
while (!f2){
f2 = 1;
int lx = rand() % (10) + 1;
int ly = rand() % (10) + 1;
for (int nx = lx - 1; nx <= lx + fx[dir2][0] * k + 1; nx++)
for (int ny = ly - 1; ny <= ly + fx[dir2][1] * k + 1; ny++)
if (b2[nx][ny] == 1){ f2 = 0; break; }
for (int nx = lx; nx <= lx + fx[dir2][0] * k; nx++)
for (int ny = ly; ny <= ly + fx[dir2][1] * k; ny++)
if (b2[nx][ny] == 3){ f2 = 0; break; }
if (f2){
for (int jx = lx, jy = ly; jx <= lx + fx[dir2][0] * k && jy <= ly + fx[dir2][1] * k; jx += fx[dir2][0], jy += fx[dir2][1])
b2[jx][jy] = 1;
}
}
int a = 1;
}
}
}
void reset(){
system("cls");
color(15); gotoxy(18, 10); printf("按 0 重排戰船; 按任意鍵開始與電腦對戰");
color(9);
gotoxy(0, 9); printf("玩家海域");
gotoxy(0, 22); printf("電腦海域");
rest[1][2] = rest[2][2] = 1;
rest[1][3] = rest[2][3] = 2;
rest[1][4] = rest[2][4] = 1;
rest[1][5] = rest[2][5] = 1;
for (int i = 1; i <= 10; i++){
b1[0][i] = b1[i][0] = b2[0][i] = b2[i][0] = 3;
b1[11][i] = b1[i][11] = b2[11][i] = b2[i][11] = 3;
}
color(8);
for (int i = 1; i <= 10; i++)for (int j = 1; j <= 10; j++) c1[i][j] = c2[i][j] = 8;
for (int i = 1; i <= 10; i++)for (int j = 1; j <= 10; j++){
b1[i][j] = b2[i][j] = 0;
a1[i][j] = "□"; gotoxy1(i, j); cout << a1[i][j];
a2[i][j] = "□"; gotoxy2(i, j); cout << a2[i][j];
}
color(14);
gotoxy1(1, -5); printf("%d*□□□□□", rest[1][5]);
gotoxy1(2, -5); printf("%d*□□□□ ", rest[1][4]);
gotoxy1(3, -5); printf("%d*□□□ ", rest[1][3]);
gotoxy1(4, -5); printf("%d*□□ ", rest[1][2]);
gotoxy2(4, 12); printf(" □□*%d", rest[2][2]);
gotoxy2(3, 12); printf(" □□□*%d", rest[2][3]);
gotoxy2(2, 12); printf(" □□□□*%d", rest[2][4]);
gotoxy2(1, 12); printf("□□□□□*%d", rest[2][5]);
color(2); for (int i = 1; i <= 10; i++){ gotoxy1(i, 11); cout << i; gotoxy2(i, 11); cout << i; }
color(6); for (int i = 1; i <= 10; i++){ gotoxy1(11, i); cout << i; gotoxy2(11, i); cout << i; }
color(7); gotoxy1(12, 4); printf("( , )"); gotoxy2(12, 4); printf("( , )");
put();
now[0][0] = now[0][1] = now[1][0] = now[1][1] = 1;
r1 = r2 = 5;
char res = _getch(); if (res == '0') reset();
}
int main(){
int gameover = 1;
while (gameover){
srand(time(NULL));
reset();
gotoxy(18, 10); printf(" ");
int haha = 0;
while (r1*r2){
if (!move1()){ haha = 1; break; } //玩家(haha==1說明中途退出)
move2(); //電腦
}
gotoxy(18, 0);
if (haha) printf("怎麼中途退出了...\n\n");
else if (r1 == 0) printf("很遺憾,你輸了...\n\n");
else if (r2 == 0) printf("恭喜你,你贏了!!!\n\n");
printf("按1退出; 按其它鍵繼續\n>>");
if (_getch() == '1') gameover = 0;
}
}
習題3.3
6.奇數派遊戲 在操場上有n>=3個人,每人都有唯一一位最近的鄰居。他們手上都有一塊奶油派,收到訊號後,都會把派扔向他最近的鄰居。假設n是奇數,而且每個人都扔的很準。請判斷對錯:至少有一個人不會被奶油派擊中。
解答:判斷正確,可以用數學歸納法證明。當n等於3時,首先尋找一個最近對,他們互相扔奶油派,剩餘一人無論扔誰,自己都不會被扔;當n=k時判斷成立,那麼當n=k+2時,首先找到一個最近對,他們互相扔奶油派。剩餘的k個人,如果他們沒有人扔向最近的兩人,問題轉化為n=k的情況,判斷成立;如果剩餘的k個人中有x個人(x>=1)扔向了最近對中的一個人,那麼還剩餘k - x個奶油派,還剩餘k個人需要判斷是否被扔,顯然不是所有人都能被扔到,判斷成立,數學歸納法得證。
習題3.4
9.八皇后問題 這是一個經典遊戲:在一個8*8的棋盤上,擺放8個皇后使得任意兩個皇后不在同一行或者同一列或者同一對角線上。那麼對下列各種情形,各有多少種不同的擺放方法?
a.任意兩個皇后不在同一塊方格上。
解答:C_6^48=4426165368
b.任意兩個皇后不在同一行上。
解答:
c.任意兩個皇后不在同一行或者同一列上。
解答:8!= 40320
同時估計在每秒能檢查100億個位置的計算機上,窮舉查詢針對上述情形找到問題的所有解需要多少時間。
解答:
10.幻方問題 n階幻方是把從1到n2的整數填入一個n階方陣,每個整數只出現一次,使得每一行、每一列、每一條主對角線上的各數之和都相等。
a. 證明:如果n階幻方存在的話,所討論的這個和一定等於n(n2+1)/2。
解答: 假設存在n階幻方,那麼n行或者n列的和都相同,這個和等於(1+n2)×n22÷n = n(n2+1)/2
b. 設計一個窮舉演算法,生成階數為n的所有幻方。
解答:不斷生成1到n2的排列,依次填入n階方陣,檢查是否滿足幻方要求。
c. 在因特網上或者圖書館查詢一個更好的生成幻方的演算法。
解答:奇數階幻方最經典的填法是羅伯法。填寫的方法是:把1(或最小的數)放在第一行正中; 按以下規律排列剩下的(n×n-1)個數:
1、每一個數放在前一個數的右上一格;
2、如果這個數所要放的格已經超出了頂行那麼就把它放在底行,仍然要放在右一列;
3、如果這個數所要放的格已經超出了最右列那麼就把它放在最左列,仍然要放在上一行;
4、如果這個數所要放的格已經超出了頂行且超出了最右列,那麼就把它放在前一個數的下一行同一列的格內;
5、如果這個數所要放的格已經有數填入,那麼就把它放在前一個數的下一行同一列的格內。
雙偶數階幻方用對稱交換法。所謂雙偶階幻方就是當n可以被4整除時的偶階幻方,即4K階幻方。在說解法之前我們先說明一個“互補數”定義:就是在 n 階幻方中,如果兩個數的和等於幻方中最大的數與 1 的和(即 n×n+1),我們稱它們為一對互補數 。如在三階幻方中,每一對和為 10 的數,是一對互補數 ;在四階幻方中,每一對和為 17 的數,是一對互補數 。
單偶數階幻方用象限對稱交換法。階數n滿足:n=4*k+2。此處以n=10為例,10=4×2+2,則k=2。
(1)把方陣分為A,B,C,D四個象限,這樣每一個象限都是奇數階。用羅伯法,依次在A象限,D象限,B象限,C象限按奇數階幻方的填法填數。
(2)從A象限的中間行、中間格開始,按自左向右的方向,標出k格。A象限的其它行則標出最左邊的k格。將這些格,和C象限相對位置上的數互換位置。
(3)從B象限的最中間列開始,自右向左,標出k-1列。(注:6階幻方由於k-1=0,所以不用再作B、D象限的資料交換), 將B象限標出的這些數,和D象限相對位置上的數進行交換,就形成幻方。
d. 實現這兩個演算法——窮舉查詢演算法以及在因特網上找的演算法,然後在自己的計算機上做一個試驗,確定在一分鐘之內,這兩個演算法能夠求出的幻方的最大階數n。
//窮舉法列印n階魔方矩陣 n <= 10
#include < iostream >
#include < conio.h >
using namespace std;
int A[11][11];
bool Flag[101]; //若K已被使用則Flag[k]=true,否則為false
int n = 3, Count = 0, Point = 0, Avr = 0;
int i_g = 1, j_g = 1, k_g = 1;
bool i_change = false;
bool j_loop = false, k_loop = false;
/////////////////////////////////////////funtion init( )
void init() // 對變數初始化
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
A[i][j] = 0;
Flag[j + (i - 1) * n] = false;
}
Count = n * n; // 矩陣的變數數
Point = n / 2; // 設定判斷點
for (int i = 1; i <= Count; i++)
Avr += i;
Avr /= n; // 矩陣每行每列之和必須 = Avr
}
////////////////////////////////////////funtion Sum( )
int Sum(int j, bool row)
{
int total = 0;
if (row == true) // 返回 j個數的最大之和(末被使用的)
for (int k = Count; j; k--)
{
if (!Flag[k])
{
total += k;
j--;
}
}
else if (row == false) // 返回 j個數的最小之和......
for (int k = 1; j; k++)
{
if (!Flag[k])
{
total += k;
j--;
}
}
return Avr - total;
}
/////////////////////////////////function Test( )
bool Test(int i, int j) //測試A[i][j]是否可行,可行返回true,否則false
{
int temp;
if (j > Point)
{
temp = 0;
for (int k = j; k >= 1; k--)
temp += A[i][k]; //計算第i行元素之和(被使用的)
if (j == n && temp != Avr)
return false;
else if (j != n && (temp < Sum(n - j, true) || temp > Sum(n - j, false)))
return false;
}
if (i > Point)
{
temp = 0;
for (int k = i; k >= 1; k--)
temp += A[k][j]; // 計算第j列元素之和(被使用的)
if (i == n && temp != Avr)
return false;
else if (i != n && (temp < Sum(n - i, true) || temp > Sum(n - i, false)))
return false;
}
temp = 0;
if (i == n && j == 1) // A[i][j]為左下角的元素
{
for (int a = i, b = j; a >= 1; a--, b++)
temp += A[a][b]; // 計算左斜線的元素之和(已全部使用)
if (temp == Avr)
return true;
else
return false;
}
if (i == n && j == n) // A[i][j]為右下角的元素
{
for (int a = i, b = j; a >= 1; a--, b--)
temp += A[a][b]; // 計算右斜線的元素之和(已全部使用)
if (temp == Avr)
return true;
else
return false;
}
temp = 0;
if (i == j)
{
for (int a = i, b = j; a >= 1; a--, b--)
temp += A[a][b]; // 計算右斜線的元素之和(被使用的)
if (temp < Sum(n - i, true) || temp > Sum(n - i, false))
return false;
}
temp = 0;
if (i + j == n + 1)
{
for (int a = i, b = j; a >= 1; a--, b++)
temp += A[a][b]; // 計算右斜線的元素之和(被使用的)
if (temp < Sum(j - 1, true) || temp > Sum(j - 1, false))
return false;
else
return true;
}
return true;
}
/////////////////////////////function back()
bool back() // 進行回溯
{
if (j_g == 1)
{
j_loop = false;
Flag[A[i_g - 1][n - 1]] = false;
Flag[A[i_g - 1][n]] = false;
k_g = A[i_g - 1][n - 1];
A[i_g - 1][n] = -1;
A[i_g - 1][n - 1] = -1;
i_g--;
i_change = true;
j_g = n - 1;
if (k_g == Count) //如果A[ i_g - 1 ][ n - 1]取的已是最後一個元素,則再進行一次回溯
back();
}
else
{
Flag[A[i_g][j_g - 1]] = false;
k_g = A[i_g][j_g - 1];
A[i_g][j_g - 1] = -1;
j_g--;
if (k_g == Count)//如果A[ i_g ][ j_g - 1]取的已是最後一個元素,則再進行一次回溯
back();
}
return true;
}
//////////////////////function Work( )
void Work()
{
for (; i_g <= n; i_g++)
{
j_loop = true;
for (; j_g <= n && j_loop; j_g++)
{
k_loop = true;
for (; k_g <= Count && k_loop; k_g++)
{
if (Flag[k_g]) //k已被使用
{
if (k_g == Count) //不能再取值了,進行回溯
{
back();
j_g--;
if (i_change)
{
i_g--;
j_loop = false;
}
k_loop = false;
i_change = false;
}
else // 取值末到末尾,考慮下一個元素
continue;
}
else // k末被使用。
{
A[i_g][j_g] = k_g; // 賦值
Flag[k_g] = true; // 標誌K已被使用
if (i_g > Point || j_g > Point) // 超過判斷點進行判斷
{
if (!Test(i_g, j_g)) // A[i][j]不可取
{
A[i_g][j_g] = -1; // 取消賦值
Flag[k_g] = false; // 取消標誌
if (k_g != Count) // 取值末到末尾,考慮下一個元素
continue;
else // 取值到末尾,不能再取了,進行回溯
{
back();
j_g--;
if (i_change)
{
i_g--;
j_loop = false;
}
k_loop = false;
i_change = false;
}
}
else // A[i][j]可取
{
if (j_g == n) // 如果j迴圈到末尾,則從頭開始取值
{
j_g = 0;
k_g = 0;
k_loop = false;
j_loop = false;
}
else // j末到末尾
{
k_loop = false;
k_g = 0;
}
}
}
else // 末超過判斷點
{
if (j_g == n) // 如果j迴圈到末尾,則從頭開始取值
{
j_g = 0;
k_g = 0;
k_loop = false;
j_loop = false;
}
else // j末到末尾
{
k_loop = false;
k_g = 0;
}
}
}
} // end k_g
} // end j_g
} // end i_g
}
void main()
{
cout << "Please input n:";
cin >> n;
init();
Work();
for (int i = 1; i <= n; i++) // 列印矩陣
{
for (int j = 1; j <= n; j++)
cout << " " << A[i][j];
cout << endl;
}
_getch();
}
一分鐘最多能計算4階幻方
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define maxn 1000
int m[maxn][maxn];
void OddMagic(int n) //奇數階幻方
{
memset(m, 0, sizeof(m)); //幻方清零
int x = 0, y = n / 2;
for (int i = 1; i <= n*n; i++)
{
m[x][y] = i;
x--; //依次沿右上角填充
y++;
if (x<0 && y>n - 1) { x = x + 2; y = y - 1; } //沿對角線超出
else if (x<0) x = x + n; //沿上邊界超出
else if (y>n - 1) y = y - n; //沿右邊界超出
else if (m[x][y] != 0) { x = x + 2; y = y - 1; } //右上角已填充
}
}
void DoubleEvenMagic(int n) //雙偶數階幻方
{
memset(m, 0, sizeof(m)); //幻方清零
for (int i = 1, x = 0, y = 0; i <= n*n; i++) //依次按順序賦初值
{
m[x][y] = i;
y++;
if (y>n - 1) { x++; y -= n; }
}
for (int i = 0; i<n; i++) //將幻方分解成m*m個4階幻方,並將每個4階幻方的對角線元素換成其互補數
for (int j = 0; j<n; j++)
if (i % 4 == 0 && j % 4 == 0) //左對角線
for (int k = 0; k<4; k++)
m[i + k][j + k] = (n*n + 1) - m[i + k][j + k];
else if (i % 4 == 3 && j % 4 == 0) //右對角線
for (int k = 0; k<4; k++)
m[i - k][j + k] = (n*n + 1) - m[i - k][j + k];
}
void SingleEvenMagic(int n) //單偶數階幻方
{
memset(m, 0, sizeof(m)); //幻方清零
int n0 = n / 2;
OddMagic(n0); //將幻方分解成2*2個奇數階幻方,呼叫奇數階幻方函式填充左上角奇數階幻方
for (int i = 0; i<n0; i++)
for (int j = 0; j<n0; j++)
{
m[i + n0][j + n0] = m[i][j] + n0*n0; //填充右下角奇數階幻方
m[i][j + n0] = m[i + n0][j + n0] + n0*n0; //填充右上角奇數階幻方
m[i + n0][j] = m[i][j + n0] + n0*n0; //填充左下角奇數階幻方
}
int k = (n - 2) / 4; //滿足公式n=4*k+2
for (int i = 0; i<n0; i++)
for (int j = 0; j<k; j++)
if (i == n0 / 2) swap(m[i][i + j], m[i + n0][i + j]); //將左上角幻方最中間元素從左向右的k個元素與左下角幻方相應位置元素交換
else swap(m[i][j], m[i + n0][j]); //將左上角幻方除最中間行外的每行前k個元素與左下角幻方相應位置元素交換
for (int i = 0; i<n0; i++)
for (int j = n0 + n0 / 2; j>n0 + n0 / 2 - (k - 1); j--)
swap(m[i][j], m[i + n0][j]); //將右上角幻方最中間列起從右向左的k-1列元素與右下角幻方相應位置元素交換
}
bool Check(int n)
{
int cnt = n*(n*n + 1) / 2; //每行每列以及對角線之和
for (int i = 0; i<n; i++)
{
int sum_row = 0, sum_line = 0;
for (int j = 0; j<n; j++)
{
sum_row += m[i][j]; //檢查各行
sum_line += m[j][i]; //檢查各列
}
if (sum_row != cnt || sum_line != cnt) return false;
}
int sum_left = 0, sum_right = 0;
for (int i = 0; i<n; i++)
{
sum_left += m[i][i]; //檢查左對角線
sum_right += m[n - i - 1][i]; //檢查右對角線
}
if (sum_left != cnt || sum_right != cnt) return false;
return true;
}
void print(int n) //按格式輸出
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
//if (j)printf(" ");
if (j == n - 1) printf("%6d\n", m[i][j]);
else printf("%6d", m[i][j]);
}
}
int main()
{
ios::sync_with_stdio(false);
int n;
while (cin >> n)
{
if (n<3) cout << "Impossible" << endl;
else if (n & 1) { OddMagic(n); if (Check(n)) print(n); } //輸出奇數階幻方
else if (!(n % 4)) { DoubleEvenMagic(n); if (Check(n)) print(n); } //輸出雙偶數階幻方
else { SingleEvenMagic(n); if (Check(n)) print(n); } //輸出單偶數階幻方
}
return 0;
}
列印n=1000時的幻方所需時間約為1分鐘
11.字母算術 有一種稱為密碼算術的算式謎題,它的算式中,所有的數字都被字母所代替。如果該算式中的單詞是有意義的,那麼這種算式被稱為字母算式題。最著名的字母算式是由大名鼎鼎的英國謎題大師亨利·E.杜德尼給出的:
這裡有兩個假設:第一,字母和十進位制數字之間是一一對應的關係,也就是說,每個字母只代表一個數字,而且不同的字母代表不同的數字;第二,數字0不出現在任何數的最左邊。求解一個字母算數意味著找到每個字母代表的是哪個數字。請注意,解可能並不是唯一的,不同人的解可能並不相同。
a. 寫一個程式用窮舉查詢解密碼算術謎題。假設給定的算式是兩個單詞的加法算式。
#include <stdio.h>
bool func(int s, int e, int n, int d, int m, int o, int r, int y)
{
int hash[10] = { 0 };
hash[s]++;
hash[e]++;
hash[n]++;
hash[d]++;
hash[m]++;
hash[o]++;
hash[r]++;
hash[y]++;
for (int i = 0; i < 10; i++)
{
if (hash[i] > 1)
return false;
}
return true;
}
int main()
{
int s,e,n,d,m,o,r,y;
int sum1, sum2, sum3;
for (s = 1; s<10; s++)
{
for (e = 0; e<10; e++)
{
for (n = 0; n < 10; n++)
{
for (d = 0; d < 10; d++)
{
for (m = 1; m < 10; m++)
{
for (o = 0; o < 10; o++)
{
for (r = 0; r < 10; r++)
{
for (y = 0; y < 10; y++)
{
sum1 = s * 1000 + e * 100 + n * 10 + d;
sum2 = m * 1000 + o * 100 + r * 10 + e;
sum3 = m * 10000 + o * 1000 + n * 100 + e * 10 + y;
if (sum1+sum2 == sum3&&func(s, e, n, d, m, o, r, y))
{
printf(" %d%d%d%d\n", s, e, n, d);
printf(" +%d%d%d%d\n", m, o, r, e);
printf("=%d%d%d%d%d\n", m, o, n, e, y);
}
}
}
}
}
}
}
}
}
return 0;
}
b. 杜德尼的謎題發表於1924年,請用你認為合理的方法解該謎題。
解答:首先加法的進位最多1,所以M=1 那麼M+S要進位至少為10,而百位最多能給一個進位,那麼S=9或者8。假設S為8 根據剛才推斷此時千位為10,進1後剩餘0,所以O為0,O為0的話E也必須是9才能進位,則進位剩餘為0,則N=0 矛盾。並且十位的N=0和R必須進位且剩餘E=8,也不可能矛盾。所以S=9。同樣此時O就可能為0或者1,因為M已經等於1,所以O=0,現在百位E+O(O=0)(可能有進位)=N,因為E≠N 所以十位必須有進位,則N=E+1。那麼N+R=E+10或者N+R+1=E+10,則R必須為8或者9,因為S已經為9,所以R只能為8,那麼個位也必須進位了,可知 D+E=10+Y。這個時候從選擇E入手(使得D,E,N,Y都不同,且D+E=10+Y ,N=E+1,而且不能為已有的9(S),8(R),0(O),1(M))結合以上條件就可以得出E=5,其餘也出來了。
習題3.5
10.我們可以用一個代表起點的頂點、一個代表重點的頂點、若干個代表死衚衕和通道的頂點來對迷宮建模,迷宮中的通道不止一條,我們必須求出連線起點和終點的迷宮道路。
a.為下圖的迷宮構造一個圖。
b.如果你發現自己身處一個迷宮中,你會選用DFS遍歷還是BFS遍歷?為什麼?
解答:我會選擇DFS遍歷。深度優先遍歷過程中,每次遇到走不通的節點,則回溯到上一個節點,繼續進行深度優先搜尋,一旦找到出口就結束了。而廣度優先搜尋具有層次的概念,需要遍歷完當前節點的所有的相鄰節點,這在走迷宮的時候相當於走了很多回頭路,效率相比深度優先搜尋而言很差。
11.三壺問題 西蒙·丹尼斯·泊松是著名的法國數學家和物理學家。據說在他遇到某個古老的謎題之後,就開始對數學感興趣了,這個謎題是這樣的:給定一個裝滿水的8品脫壺以及兩個容量分別為5品脫和3品脫的空壺,如何通過完全灌滿或者倒空這些壺從而使得某個壺精確地裝有4品脫的水?用廣度優先查詢來解這個謎題。
分析:解法:可以把每次三個水壺中水的量組成一個狀態,比如初始狀態為008,對應第一個水壺0品脫水,第二個水壺0品脫水,第三個水壺8品脫水。對題目的狀態空間圖進行廣度優先遍歷。當表示狀態的數字中出現4時,即求出答案。
1、為了打印出倒水的過程,需要宣告一個前置狀態儲存當前狀態由哪個狀態轉換而來,然後就可以回溯到初始狀態,打印出倒水過程。相當於樹中的父結點。
2、可以宣告一個map表,儲存已有的狀態,對已有的狀態,就不再向下繼續遍歷,這樣可以節省求解時間。
3、因為是廣度優先遍歷,所以第一次解得的答案所需的倒水的次數最少,解為最優解。
#include <iostream>
#include <vector>
#include <map>
#define MaxFirst 3
#define MaxSecond 5
#define MaxThird 8
using namespace std;
class State
{
public:
int second;
int num[3];
State* preState;
static map<int, int> mapping;
public:
State(int first, int second, int third)
{
num[0] = first;
num[1] = second;
num[2] = third;
}
void init()
{
mapping[0] = MaxFirst;
mapping[1] = MaxSecond;
mapping[2] = MaxThird;
}
bool canPour(int from, int to)//判斷是否可以從from水壺中倒水到to水壺中
{
if (num[from] == 0)
{
return false;
}
if (num[to] == mapping[to])
{
return false;
}
else
{
return true;
}
}
void pour(int from, int to)//倒水過程
{
if (num[from] + num[to]>mapping[to])
{
num[from] = num[from] - (mapping[to] - num[to]);
num[to] = mapping[to];
}
else
{
num[to] = num[to] + num[from];
num[from] = 0;
}
}
};
map<int, int> State::mapping;
int main()
{
map<int, int> states;
State *start = new State(0, 0, 8);
start->init();
State *state = start;
State *endState = new State(8, 8, 8);//只有獲得解endState才會改變,賦值全為8為了方便判斷是否獲得最終解
vector<State> action;//儲存所有狀態物件
action.push_back(*start);//把初始狀態先加入佇列中
int n = 0;
do{
for (int i = 0; i<3; i++)//雙層迴圈為從i水壺中倒水入j水壺中
{
for (int j = 0; j<3; j++)
{
if (i != j)
{
if (state->canPour(i, j))
{
state->pour(i, j);
if (states[state->num[0] * 100 + state->num[1] * 10 + state->num[2]] == 0)//如果該狀態不在hash表中,即為第一次出現該狀態
{
states[state->num[0] * 100 + state->num[1] * 10 + state->num[2]]++;
(state->preState) = new State(action[n]);
action.push_back(*state);
if (state->num[0] == 4 || state->num[1] == 4 || state->num[2] == 4)//獲得解
{
endState = state;
i = 4;
break;
}
}
}
}
*state = action[n];
}
}
n++;
} while (endState->num[0] == 8 && endState->num[1] == 8 && n<action.size());
cout << endState->num[0] << " " << endState->num[1] << " " << endState->num[2] << endl;
state = endState;
do
{
state = state->preState;
cout << state->num[0] << " " << state->num[1] << " " << state->num[2] << endl;
} while (state->num[2] != 8);
return 0;
}