using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
struct JyoutaiDef
{
internal int CoinMaisuu;
internal bool[,] CoinHaitiArr;
}
static void Main()
{
//4*4のコインの配置を列挙
Console.WriteLine("4*4のコインの配置を列挙中。経過時間={0}", sw.Elapsed);
JyoutaiDef[] CoinHaiti_4_4_Arr = DeriveCoinHaiti_4_4_Arr();
Console.WriteLine("4*4のコインの配置数={0}。経過時間={1}",
CoinHaiti_4_4_Arr.Length, sw.Elapsed);
Console.WriteLine();
//上の8*4のコインの配置を列挙
Console.WriteLine("上の8*4のコインの配置を列挙中。経過時間={0}", sw.Elapsed);
JyoutaiDef[] CoinHaiti_Ue_8_4_Arr = DeriveCoinHaiti_Ue_8_4_Arr(CoinHaiti_4_4_Arr);
Console.WriteLine("上の8*4のコインの配置数={0}。経過時間={1}",
CoinHaiti_Ue_8_4_Arr.Length, sw.Elapsed);
Console.WriteLine();
//下の8*4のコインの配置を列挙
Console.WriteLine("下の8*4のコインの配置を列挙中。経過時間={0}", sw.Elapsed);
JyoutaiDef[] CoinHaiti_Shita_8_4_Arr = DeriveCoinHaiti_Shita_8_4_Arr(CoinHaiti_Ue_8_4_Arr);
Console.WriteLine("下の8*4のコインの配置数={0}。経過時間={1}",
CoinHaiti_Shita_8_4_Arr.Length, sw.Elapsed);
Console.WriteLine();
//8*8のコインの配置を列挙
Console.WriteLine("8*8のコインの配置を列挙中。経過時間={0}", sw.Elapsed);
DeriveAnswer(CoinHaiti_Ue_8_4_Arr, CoinHaiti_Shita_8_4_Arr);
}
//4*4のコインの配置を列挙
static JyoutaiDef[] DeriveCoinHaiti_4_4_Arr()
{
var WillReturnCoinHaiti_4_4_List = new List<JyoutaiDef>();
var stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
WillPush.CoinMaisuu = 0;
WillPush.CoinHaitiArr = new bool[4, 4];
stk.Push(WillPush);
while (stk.Count > 0) {
JyoutaiDef Popped = stk.Pop();
//外周を1マス増やしたとしても閉路があったら枝切り
if (HasHeiro_4_4(Popped.CoinHaitiArr, Popped.CoinMaisuu)) {
continue;
}
//コイン2枚以上なら候補としてAdd
if (Popped.CoinMaisuu >= 2) {
JyoutaiDef WillAdd;
WillAdd.CoinMaisuu = Popped.CoinMaisuu;
WillAdd.CoinHaitiArr = Popped.CoinHaitiArr;
WillReturnCoinHaiti_4_4_List.Add(WillAdd);
}
int CountCoinMaisuu = 0;
bool WillBreakYLoop = false;
for (int Y = 0; Y <= 3; Y++) {
for (int X = 0; X <= 3; X++) {
if (Popped.CoinMaisuu > 0) {
if (CountCoinMaisuu < Popped.CoinMaisuu) {
if (Popped.CoinHaitiArr[X, Y])
CountCoinMaisuu++;
//配置済コイン数に到達するまでは、Continue
continue;
}
}
//配置済コインと隣接していたら配置しない
if (IsRinsetuCoin(Popped.CoinHaitiArr, X, Y)) continue;
//コインの配置処理
WillPush.CoinMaisuu = Popped.CoinMaisuu + 1;
WillPush.CoinHaitiArr = (bool[,])Popped.CoinHaitiArr.Clone();
WillPush.CoinHaitiArr[X, Y] = true;
//Y座標が3以上で配置コインが1枚未満なら枝切り
if (3 <= Y && WillPush.CoinMaisuu < 1) {
WillBreakYLoop = true; break;
}
stk.Push(WillPush);
}
if (WillBreakYLoop) break;
}
}
//コインの枚数の降順にソートしておく
return WillReturnCoinHaiti_4_4_List.OrderByDescending(X => X.CoinMaisuu).ToArray();
}
//外周を1マス増やしたとしても閉路があるかを返す(4*4用)
static bool HasHeiro_4_4(bool[,] pCoinArr_4_4, int pCoinMaisuu)
{
bool[,] CoinArr_6_6 = new bool[6, 6];
for (int X = 0; X <= 3; X++) {
for (int Y = 0; Y <= 3; Y++) {
CoinArr_6_6[X + 1, Y + 1] = pCoinArr_4_4[X, Y];
}
}
int VisitedCnt = ExecUnionFind(CoinArr_6_6, 2, CoinArr_6_6[2, 2] ? 3 : 2);
return (VisitedCnt + pCoinMaisuu < 6 * 6);
}
//指定した座標にコインが隣接しているかを返す
static bool IsRinsetuCoin(bool[,] pCoinHaitiArr, int pX, int pY)
{
if (pX - 1 >= 0 && pCoinHaitiArr[pX - 1, pY]) return true;
if (pY - 1 >= 0 && pCoinHaitiArr[pX, pY - 1]) return true;
return false;
}
//盤面の上半分の8*4のコインの配置を列挙
static JyoutaiDef[] DeriveCoinHaiti_Ue_8_4_Arr(JyoutaiDef[] pCoinHaiti_4_4_Arr)
{
var WillReturnCoinHaiti_Ue_8_4_List = new List<JyoutaiDef>();
foreach (JyoutaiDef Jyoutai1 in pCoinHaiti_4_4_Arr) {
foreach (JyoutaiDef Jyoutai2 in pCoinHaiti_4_4_Arr) {
//コインの枚数が9枚未満なら枝切り
int CoinMaisuuSum = Jyoutai1.CoinMaisuu + Jyoutai2.CoinMaisuu;
if (CoinMaisuuSum < 9) break;
//コインの隣接のチェック
bool HasRinsetuCoin = false;
for (int Y = 0; Y <= 3; Y++) {
if (Jyoutai1.CoinHaitiArr[3, Y] && Jyoutai2.CoinHaitiArr[0, Y]) {
HasRinsetuCoin = true; break;
}
}
if (HasRinsetuCoin) continue;
//値をコピー
bool[,] CoinHaitiArr = new bool[8, 4];
for (int X = 0; X <= 3; X++) {
for (int Y = 0; Y <= 3; Y++) {
CoinHaitiArr[X, Y] = Jyoutai1.CoinHaitiArr[X, Y];
CoinHaitiArr[X + 4, Y] = Jyoutai2.CoinHaitiArr[X, Y];
}
}
//最低1枚のコインを配置可能なマスに配置済かチェック
if (IsSetCoinLatest1(CoinHaitiArr) == false) continue;
//閉路のチェック
if (HasHeiro_Ue_8_4(CoinHaitiArr, CoinMaisuuSum)) continue;
JyoutaiDef WillAdd;
WillAdd.CoinMaisuu = CoinMaisuuSum;
WillAdd.CoinHaitiArr = CoinHaitiArr;
WillReturnCoinHaiti_Ue_8_4_List.Add(WillAdd);
}
}
//コインの枚数の降順にソートしておく
return WillReturnCoinHaiti_Ue_8_4_List.OrderByDescending(X => X.CoinMaisuu).ToArray();
}
//最低1枚のコインを配置可能なマスに配置済かチェック
static bool IsSetCoinLatest1(bool[,] pCoinHaitiArr)
{
//最低1枚のコインがあるか?
Func<int, int, int, int, bool> HasCoin = (pFromX, pFromY, pToX, pToY) =>
{
for (int X = pFromX; X <= pToX; X++) {
for (int Y = pFromY; Y <= pToY; Y++) {
if (pCoinHaitiArr[X, Y]) return true;
}
}
return false;
};
if (HasCoin(0, 0, 1, 1) == false) return false;
if (HasCoin(1, 0, 3, 1) == false) return false;
if (HasCoin(2, 0, 4, 1) == false) return false;
if (HasCoin(3, 0, 5, 1) == false) return false;
if (HasCoin(4, 0, 6, 1) == false) return false;
if (HasCoin(6, 0, 7, 1) == false) return false;
if (HasCoin(0, 1, 1, 3) == false) return false;
if (HasCoin(1, 1, 3, 3) == false) return false;
if (HasCoin(2, 1, 4, 3) == false) return false;
if (HasCoin(3, 1, 5, 3) == false) return false;
if (HasCoin(4, 1, 6, 3) == false) return false;
if (HasCoin(6, 1, 7, 3) == false) return false;
return true;
}
//下に1行増やしたとしても閉路があるかを返す(8*4用)
static bool HasHeiro_Ue_8_4(bool[,] pCoinArr_8_4, int pCoinMaisuu)
{
bool[,] CoinArr_8_5 = new bool[8, 5];
for (int X = 0; X <= 7; X++) {
for (int Y = 0; Y <= 3; Y++) {
CoinArr_8_5[X, Y] = pCoinArr_8_4[X, Y];
}
}
int VisitedCnt = ExecUnionFind(CoinArr_8_5, 3, CoinArr_8_5[3, 1] ? 2 : 1);
return (VisitedCnt + pCoinMaisuu < 8 * 5);
}
//盤面の下半分の8*4のコインの配置を列挙
static JyoutaiDef[] DeriveCoinHaiti_Shita_8_4_Arr(JyoutaiDef[] pCoinHaiti_Ue_8_4_Arr)
{
var WillReturnCoinHaiti_Shita_8_4_List = new List<JyoutaiDef>();
foreach (JyoutaiDef AnyJyoutai in pCoinHaiti_Ue_8_4_Arr) {
int SetCoinMaisuu = AnyJyoutai.CoinMaisuu;
//値をコピー
bool[,] CoinHaitiArr = new bool[8, 4];
for (int X = 0; X <= 7; X++) {
for (int Y = 0; Y <= 3; Y++) {
CoinHaitiArr[X, 3 - Y] = AnyJyoutai.CoinHaitiArr[X, Y];
}
}
JyoutaiDef WillAdd;
WillAdd.CoinMaisuu = SetCoinMaisuu;
WillAdd.CoinHaitiArr = CoinHaitiArr;
WillReturnCoinHaiti_Shita_8_4_List.Add(WillAdd);
}
//コインの枚数の降順にソートしておく
return WillReturnCoinHaiti_Shita_8_4_List.OrderByDescending(X => X.CoinMaisuu).ToArray();
}
//8*8のコインの配置を列挙
static void DeriveAnswer(JyoutaiDef[] pCoinHaiti_Ue_8_4_Arr, JyoutaiDef[] pCoinHaiti_Shita_8_4_Arr)
{
int MaxCoinCnt = 0;
int AnswerCnt = 0;
foreach (JyoutaiDef Jyoutai1 in pCoinHaiti_Ue_8_4_Arr) {
foreach (JyoutaiDef Jyoutai2 in pCoinHaiti_Shita_8_4_Arr) {
//コインの枚数が現在の解未満なら枝切り
int CoinMaisuuSum = Jyoutai1.CoinMaisuu + Jyoutai2.CoinMaisuu;
if (CoinMaisuuSum < MaxCoinCnt) break;
//コインの隣接のチェック
bool HasRinsetuCoin = false;
for (int X = 0; X <= 7; X++) {
if (Jyoutai1.CoinHaitiArr[X, 3] && Jyoutai2.CoinHaitiArr[X, 0]) {
HasRinsetuCoin = true; break;
}
}
if (HasRinsetuCoin) continue;
//値をコピー
bool[,] CoinHaitiArr = new bool[8, 8];
for (int X = 0; X <= 7; X++) {
for (int Y = 0; Y <= 3; Y++) {
CoinHaitiArr[X, Y] = Jyoutai1.CoinHaitiArr[X, Y];
CoinHaitiArr[X, Y + 4] = Jyoutai2.CoinHaitiArr[X, Y];
}
}
//閉路のチェック
if (HasHeiro_8_8(CoinHaitiArr, CoinMaisuuSum)) continue;
if (MaxCoinCnt < CoinMaisuuSum) {
MaxCoinCnt = CoinMaisuuSum;
AnswerCnt = 0;
}
AnswerCnt++;
if (AnswerCnt % 2000 == 1) {
Console.WriteLine("解候補{0}(コインの枚数={1})を発見。経過時間={2}",
AnswerCnt, CoinMaisuuSum, sw.Elapsed);
}
if (AnswerCnt == 1) PrintAnswer(CoinHaitiArr);
}
}
Console.WriteLine("解は{0}通りで、コインの枚数={1}。経過時間={2}",
AnswerCnt, MaxCoinCnt, sw.Elapsed);
}
//閉路があるかを返す(8*8用)
static bool HasHeiro_8_8(bool[,] pCoinArr_8_8, int pCoinMaisuu)
{
int VisitedCnt = ExecUnionFind(pCoinArr_8_8, 3, pCoinArr_8_8[3, 3] ? 4 : 3);
return (VisitedCnt + pCoinMaisuu < 8 * 8);
}
struct PointDef
{
internal int X;
internal int Y;
}
static int ExecUnionFind(bool[,] pCoinHaitiArr, int pStaX, int pStaY)
{
var VisitedSet = new HashSet<int>(); //訪問済座標のセット
var stk = new Stack<PointDef>();
PointDef WillPush;
WillPush.X = pStaX;
WillPush.Y = pStaY;
VisitedSet.Add(pStaX * 10 + pStaY);
stk.Push(WillPush);
while (stk.Count > 0) {
PointDef Popped = stk.Pop();
Action<int, int> PushSyori = (pNewX, pNewY) =>
{
//コインの配置マスなら枝切り
if (pCoinHaitiArr[pNewX, pNewY]) return;
//訪問済なら枝切り
if (VisitedSet.Add(pNewX * 10 + pNewY) == false)
return;
WillPush.X = pNewX;
WillPush.Y = pNewY;
stk.Push(WillPush);
};
//左に移動
if (Popped.X > 0) PushSyori(Popped.X - 1, Popped.Y);
//右に移動
if (Popped.X < pCoinHaitiArr.GetUpperBound(0)) PushSyori(Popped.X + 1, Popped.Y);
//上に移動
if (Popped.Y > 0) PushSyori(Popped.X, Popped.Y - 1);
//下に移動
if (Popped.Y < pCoinHaitiArr.GetUpperBound(1)) PushSyori(Popped.X, Popped.Y + 1);
}
return VisitedSet.Count;
}
//解を出力
static void PrintAnswer(bool[,] pCoinHaitiArr)
{
var sb = new System.Text.StringBuilder();
for (int Y = 0; Y <= pCoinHaitiArr.GetUpperBound(1); Y++) {
for (int X = 0; X <= pCoinHaitiArr.GetUpperBound(0); X++) {
sb.Append(pCoinHaitiArr[X, Y] ? '●' : '□');
}
sb.AppendLine();
}
Console.WriteLine(sb.ToString());
}
}