マルチスレッド版数独自動生成ソフトC++コードを題材とする超初心者のためのVisual Studio C++講義
第6章 6次魔方陣・8次魔方陣の作成

第13話 8以上の4の倍数次魔方陣の考え方

第11話と第12話で革命が起きたと書きましたが、

第28回 新魔方陣の法則(偶数次版魔方陣の法則)第1弾

第29回 新魔方陣の法則(偶数次版魔方陣の法則)第2弾

を読み返すと、判断が少し早すぎました。

第13話では8以上の4の場合数次魔方陣の場合について見ていきます。

つまり、8,12,16,・・・という系列の魔方陣の場合について見ていくということです。

理由はこちらの場合の方が、単純であるからです。

1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64


(同じもの図にしました)

この図を見て
#include<iostream>//インクルードファイルiostreamの読み込み

#include<conio.h>//while(!_kbhit());を使うためのお呪い

#include<string> //文字列変数を使えるようにするために組み込む

#include <iomanip> //setprecisionを使えるように組み込む

#include <cmath>//powなどを使うときに必要


#include <ctime>//time()(←現時刻発生する関数)を使うために必要

using namespace std;//coutを使うときに必要なお呪い

void 魔方陣();//横と縦の2方向を持つ2次元for文体験

const int n = 8;//これからは具体的な数字を使わずにnとする。左の8を12などに変更すれば12次魔方陣に対応する
//n = 6 の一か所だけ具体的な数字を使い、他ではすべて n を使ってください。

int main() {//私は社長だ。

  魔方陣();

  while (!_kbhit());//待機させるための命令

  return(0);//int main()終わるためのお呪い

}

void 魔方陣() {

  int a[n][n];

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      a[i][j] = n * i + j + 1;//自然配列を生成してaに収納する

    }
  }

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) { //自然配列に体裁を整えて表示する。

      if (a[i][j] < 10)cout << " ";

       cout << a[i][j] << " ";
    }
     
    cout << endl;
  }

  cout << endl; int u;//a[i][j]などのデータを一時保存する
  //対角線部分を交換する

  for (int i = 0; i < n / 2; i++) {//2026年2月17日修正 修正内容(3 → n / 2) 修正理由具体的数字3が入ってしまっている

     u = a[i][i];
     //a[i][i]のデータがa[n - 1 - i][n - 1 - i]のデータに上書きされる前にuに保存

     a[i][i] = a[n - 1 - i][n - 1 - i]; //a[i][i]のデータをa[n - 1 - i][n - 1 - i]のデータで上書き

     a[n - 1 - i][n - 1 - i] = u; //a[n - 1 - i][n - 1 - i]のデータをa[i][i]の元データで保存

  }
  //対角線部分の交換終了

  //逆対角線部分を交換する

  for (int i = 0; i < n / 2; i++) {//2026年2月17日修正 修正内容(3 → n / 2) 修正理由 具体的数字3が入ってしまっている

    u = a[i][n - 1 - i]; //a[i][n - 1 - i]のデータがa[n - 1 - i][i]のデータに上書きされる前にuに保存

    a[i][n - 1 - i] = a[n - 1 - i][i]; //a[i][n - 1 - i]のデータをa[n - 1 - i][i]のデータで上書き

    a[n - 1 - i][i] = u; //a[n - 1 - i][i]のデータをa[i][n - 1 - i]の元データで保存

  }

   //逆対角線部分の交換終了 //明るい紫色の部分の交換

  for (int i = 0; i < n / 2; i++) {//2026年2月16日修正 修正内容(3→ n / 2) 修正理由 具体的数字3が入ってしまっている

    //1行目から4行目の左右対称移動

    u = a[i][(3 + i) % (n / 2)];//2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっている
    //a[i][(3 + i) % (n / 2)]のデータがa[i][n - 1 - ((3 + i) % (n / 2))]
    // のデータ上書きされる前にuに保存

    a[i][(3 + i) % (n / 2)] = a[i][n - 1 - ((3 + i) % (n / 2))];
    //2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[i][(3 + i) % (n / 2)]のデータをa[i][n - 1 - ((3 + i) % (n / 2))]のデータで上書き

    a[i][n - 1 - ((3 + i) % (n / 2))] = u;
    //2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[i][n - 1 - ((3 + i) % (n / 2))]のデータをa[i][(3 + i) % (n / 2)]の元データで上書き

    //1行目から4行目の左右対称移動終了


     //5行目から8行目までの左右対称移動

    u = a[n - 1 - i][(3 + i) % (n / 2)];
    //2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[n - 1 - i][(3 + i) % (n / 2)]のデータがa[n - 1 - i][n - 1 - ((3 + i) % (n / 2))]
    // のデータ上書きされる前にuに保存

    a[n - 1 - i][(3 + i) % (n / 2)] = a[n - 1 - i][n - 1 - ((3 + i) % (n / 2))];
    //2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[n - 1 - i][(3 + i) % (n / 2)]のデータをa[n - 1 - i][n - 1 - ((3 + i) % (n / 2))]のデータで上書き

    a[n - 1 - i][n - 1 - ((3 + i) % (n / 2))] = u;
    //2026年2月16日修正 修正内容(3→ (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[n - 1 - i][n - 1 - ((3 + i) % (n / 2))]のデータをa[n - 1 - i][(3 + i) % (n / 2)]の元データで上書き

    //5行目から8行目までの左右対称移動終了
  }
  //明るい紫色の部分の交換終了

  //緑の部分の交換
  for (int i = 0; i < n / 2; i++) {
    //2026年2月16日修正 修正内容(3→ n / 2) 修正理由 具体的数字3が入ってしまっていた
    //1列目から4列までの上下対称移動

    u = a[i][(2 + i) % (n / 2)];
    //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっていた
    // a[i][(2 + i) % (n / 2)]のデータがa[n - 1 - i][(2 + i) % (n / 2)]
     // のデータに上書きされる前にuに保存

    a[i][(2 + i) % (n / 2)] = a[n - 1 - i][(2 + i) % (n / 2)];
    //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっている
    //a[i][(2 + i) % (n / 2)]のデータをa[n - 1 - i][(2 + i) % (n / 2)]のデータで上書き

    a[n - 1 - i][(2 + i) % (n / 2)] = u;
    //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっている
    //a[n - 1 - i][(2 + i) % (n / 2)]のデータをa[i][(2 + i) % (n / 2)]の元データで上書き

    //1列目から4列までの上下対称移動終了


    //5列目から8列までの上下対称移動

    u = a[i][n - 1 - (2 + i) % (n / 2)];
    //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[i][(n - 1 - (2 + i) % (n / 2)]のデータがa[n - 1 - i][n - 1 - (2 + i) % (n / 2)] // のデータに上書きされる前にuに保存

   a[i][n - 1 - (2 + i) % (n / 2)] = a[n - 1 - i][n - 1 - (2 + i) % (n / 2)];
   //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっていた
    //a[i][n - 1 - (2 + i) % (n / 2)]のデータをa[n - 1 - i][n - 1 - (2 + i) % (n / 2)]のデータで上書き

   a[n - 1 - i][n - 1 - (2 + i) % (n / 2)] = u;
   //2026年2月17日修正 修正内容(% 3→ % (n / 2)) 修正理由 具体的数字3が入っていまっていた
   //a[n - 1 - i][n - 1 - (2 + i) % (n / 2)]のデータをa[i][n - 1 - (2 + i) % (n / 2)]の元データで上書き

   //5列目から8列までの上下対称移動終了
   
 }
 //緑の部分の交換終了

   int g[n];//g(行合計計算配列)の定義

  for (int i = 0; i < n; i++)g[i] = 0;//すべて0に初期化

  int r[n];//r(列合計計算配列)の定義

  for (int i = 0; i < n; i++)r[i] = 0;//すべて0に初期化

  int t[2] = { 0,0 };//t(対角線合計配列)の定義と初期化

  //各行の合計計算

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      g[i] = g[i] + a[i][j];
     }

  }

  //各列の合計計算
  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {
       
       r[i] = r[i] + a[i][j];

    }
  }

  //各対角線の合計計算
  for (int i = 0; i < n; i++) {

    t[0] = t[0] + a[i][i];

    t[1] = t[1] + a[i][n - 1 - i];
  }
  //6次魔方陣 + 各合計の表示

  cout << endl;

  for (int i = 0; i < n; i++) {

    cout << "  ";

  }

  cout << " " << t[1] << endl;

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {

      if (a[i][j] < 10)cout << " ";

      cout << " " << a[i][j] << " ";

    }

    cout << " " << g[i];

    cout << endl;
  }

  cout << endl;

  for (int i = 0; i < n; i++) {

    cout << r[i] << " ";

  }

  cout << " " << t[0] << " "; //6次魔方陣 + 各合計の表示終了
  
  //以降魔方陣を小さい順に並べ替える作業

  int c[n * n];//魔方陣を複写するための1次元配列
  //魔方陣の内容を複写のための1次元配列にコピー

  for (int i = 0; i < n; i++) {

    for (int j = 0; j < n; j++) {
 
      c[n * i + j] = a[i][j];

    }
   }

  //c[i][j]の並び替え

  for (int i = 0; i < n * n; i++) {

    int min = 100;//最小値を表すminと定義して、最小ととしてあり得ない100で初期化

    int jk;//最小値になる j を保存するための変数

    for (int j = i; j < n * n; j++) {
       if (c[j] < min) {

         min = c[j];//最小値の更新

         jk = j;//その際jの記録
       }
    }

     //c[i]のデータとc[k]のデータを交換する

    if (jk > i) {
      u = c[i];//上書きされる前にc[i]のデータを保存

      c[i] = c[jk];//c[i]のデータをc[k]のデータで上書き

      c[jk] = u;//c[k]のデータをc[i]の元データで上書き
    }

     //c[i]のデータとc[k]のデータを交換する終了

  }

  //以降魔方陣を小さい順に並べ替える作業終了

  //並び変えたデータを体裁を整えて表示   
  cout << endl << endl;

  for (int i = 0; i < n * n; i++) {
    
    if (c[i] < 10)cout << " ";
    //c[i]のデータが一桁の場合半角スペースを入れる

    cout << c[i] << " ";

    if (i % n == n - 1)cout << endl;
   }

   cout << endl;

}

はすでに普遍版を実現していると考えました。

基本具体的な数字でコーティングしておらず、

すべて n で表しているので、

const int n = 8;

の部分を

const int n = 12;

const int n = 16;

    ・

    ・

    ・

と変更していけば12次魔方陣、16次魔方陣、20次魔方陣、・・・、4 * m(mは整数) 次魔方陣、・・・

4の倍数次魔方陣に普遍版ができた早とちりしたわけですが、

第28回 新魔方陣の法則(偶数次版魔方陣の法則)第1弾を読んでいただければわかる通り、

1 2 3 4
9 10 11 12
17 18 19 20
25 26 27 28

上で左の1/4に注目して色を塗り、

後は下の線と右の線に鏡を置いて、


1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64


(同じもの図にしました)

ただし、鏡によって映し取られるものは色だけです。

ですから、


const int n = 12;

とすれば12次魔方陣

const int n = 16

とすれば16次魔方陣

ができて4倍数字の場合の普遍版ができると判断しましたが、


1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64



実は上左1/4

1 2 3 4
9 10 11 12
17 18 19 20
25 26 27 28

のブロックに注目して、右下へ下がっていく斜め行に注目したときに、

白1色、黄色1色、緑1色、明るい紫1色と解釈してしまいましたが、

1 2 3 4 5 6 7 8 9 10 11 12
13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84
85 86 87 88 89 90 91 92 93 94 95 96
97 98 99 100 101 102 103 104 105 106 107 108
109 110 111 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130 131 132
133 134 135 136 137 138 139 140 141 142 143 144


1 2 3 4 5 6
13 14 15 16 17 18
25 26 27 28 29 30
37 38 39 40 41 42
49 50 51 52 53 54
61 62 63 64 65 66

だと白だけ3行になってしまいます。

第1行目だけに注目してください。

黄色は中心に対して点対称移動

緑は中心の直線に対して左右線対称移動

明るい紫は中心の直線に対して上下線対称移動でした。

すると1行目と最終行の色は

1 2 3 4 5 6 7 8 9 10 11 12
13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84
85 86 87 88 89 90 91 92 93 94 95 96
97 98 99 100 101 102 103 104 105 106 107 108
109 110 111 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130 131 132
133 134 135 136 137 138 139 140 141 142 143 144

(本当は全部に色塗りたいのですが、ひとますずつ塗らなければならず大変肩の凝る作業なので、

お許し下さい。)

ということにあります。

黄色は中心の点に対して点対称移動

緑は中心の直線に対して上下線対称移動

明るい紫は中央の直線に対して線対称移動ですから、

第一行は

144,2,135,9,5,6,7,9,41,142,133

となります。足し算を実行してください。

144+2+135+9+5+6+7+9+41+142+133 = 633

となりますが、12次魔方陣の場合各行の和は

(1+2+3+・・・+144) ÷ 12 = 10440 ÷12 = 870 

ですから一致しません。

色塗りは黄色→白→緑→明るい紫の順に塗るのですが、

黄色と白は1色ずつです。

それだと足りません。

正しくは、全体を見る場合には

黄色2色、白2色、緑4色、明るい紫4色

であり、

上左1/4を見るときには

黄色1色、白1色、緑2色、明るい紫2色

なのです。ですから、

黄色→白→緑→明るい紫→緑→明るい紫

なのです(正確には、黄色は先頭に来なければなりませんが、

白は1色、緑2色、明るい紫2色の原則が守られれば順番は自由です)。

すると、

1 2 3 4 5 6 7 8 9 10 11 12
13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70 71 72
73 74 75 76 77 78 79 80 81 82 83 84
85 86 87 88 89 90 91 92 93 94 95 96
97 98 99 100 101 102 103 104 105 106 107 108
109 110 111 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130 131 132
133 134 135 136 137 138 139 140 141 142 143 144

すると第1行は

144,2,135,9,137,7,6,140,4,142,11,133から

144+2+135+9+137+7+6+140+4+142+11+133 = 870

で一致します。

念のために最終行も見ておくと

12,134,3,141,5,139,138,8,136,10,143,1から

12+124+3+141+5+139+138+8+136+10+143+1 = 870

でご名答です。

1 2 3 4 5 6
13 14 15 16 17 18
25 26 27 28 29 30
37 38 39 40 41 42
49 50 51 52 53 54
61 62 63 64 65 66

は正しくは

1 2 3 4 5 6
13 14 15 16 17 18
25 26 27 28 29 30
37 38 39 40 41 42
49 50 51 52 53 54
61 62 63 64 65 66

なのです。

16魔方陣なら 上左1/4を見るときには

黄色1色、白1色、緑3色、明るい紫3色

です。

20魔方陣なら 上左1/4を見るときには

黄色1色、白1色、緑4色、明るい紫4色

です。

明確な法則性があります。

黄色1色、白1色

は固定です。

すると12次場合は

(12 / 2 - 2) ÷ 2 = 2

の計算から緑と明るい紫はそれぞれ2色となります。

16次場合は

(16 / 2 - 2) ÷ 2 = 3

の計算から緑と明るい紫はそれぞれ3色となります。

20次の場合は

(20 / 2 - 2) ÷2 = 4

の計算から緑と明るい紫はそれぞれ4色となります。

では、コードを変更して8以上の4の倍数次魔方陣のコードを作り、

8以上の4の倍数次魔方陣普遍版を完成させてください。

第14話では今回の主張が正しいことを数学的に証明して、

第15話で答え合わせをしていきます。

    





第6章第12話へ 第6章第14話へ

本講義トップへ