トップページに戻る
次のC#のサンプルへ
前のC#のサンプルへ
Cマガ電脳クラブ(第064回) 1996
問題
Fig.1は1996年にちなんだ何の変哲もない式に見える。
ところがこの式、本誌を逆さまに見ても成り立っているのだ!
3桁までの数を6個も使っているのが少々野暮ったいので、
2桁までの数を5個以内、演算記号は×と+だけとして、
逆からも成り立つ式をすべて見つけてほしい。
積や和の順序を入れ換えただけのものは同じとみなすことにする。もちろん右辺は1996だ。
また、3や80などの数は使えない。3は逆から読めないし、80は08という変な表記になるからだ。
Fig.1 1996
ソース
using System;
using System.Collections.Generic;
class Program
{
struct JyoutaiDef
{
internal int NumCnt;
internal string Expression;
internal int CalcResult;
internal bool IsAscAll; //各項の値が昇順が昇順で、掛け算も昇順か?
internal bool IsAscOmitLast; //各項の値(最後の項以外)が昇順が昇順で、掛け算も昇順か?
internal bool IsSyokouOver; //最初の項が1996/2以下か?
internal bool Is2kouOver; /*最初の項+2項目 < 1996の場合
最初の項+2項目*2 >1996 でないか?*/
internal bool MustKakezanIsOK; //掛け算を行った後の、単項の加算がないか?
}
static void Main()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
//各項の候補になる2桁までの数の配列
int[] KouhoNumArr = DeriveKouhoNumArr();
WillPush.Expression = ""; WillPush.CalcResult = 0;
WillPush.NumCnt = 1;
WillPush.IsAscAll = WillPush.IsAscOmitLast = true;
WillPush.IsSyokouOver = false;
WillPush.Is2kouOver = false;
WillPush.MustKakezanIsOK = true;
foreach (int EachNum in KouhoNumArr) {
WillPush.Expression = EachNum.ToString();
WillPush.CalcResult = EachNum;
stk.Push(WillPush);
}
int AnswerCnt = 0;
int WillOutInfoCnt = 0;
while (stk.Count > 0) {
JyoutaiDef Popped = stk.Pop();
if (++WillOutInfoCnt == 1000000) {
WillOutInfoCnt = 0;
Console.WriteLine("経過時間={0}。{1}を検証中", sw.Elapsed, Popped.Expression);
}
if (IsAnswer(Popped)) {
AnswerCnt++;
Console.WriteLine("解{0}を発見。{1}=1996", AnswerCnt, Popped.Expression);
Console.WriteLine("逆さまに見たら。9661={0}", DeriveRevExpression(Popped.Expression));
continue;
}
//数値の数で枝切り
if (Popped.NumCnt == 5) {
continue;
}
WillPush.NumCnt = Popped.NumCnt + 1;
Action<string> PushSyori = (pExpression) =>
{
WillPush.Expression = pExpression;
CheckExpression(ref WillPush);
if (IsEdakiri(WillPush) == false)
stk.Push(WillPush);
};
foreach (int EachNum in KouhoNumArr) {
if (Popped.NumCnt != 4) { //4個目の演算子は必ず*
//+をつなぐパターン
PushSyori(Popped.Expression + "+" + EachNum.ToString());
}
//*をつなぐパターン
PushSyori(Popped.Expression + "*" + EachNum.ToString());
}
}
}
//各項の候補になる2桁までの数値の配列を返す
static int[] DeriveKouhoNumArr()
{
var KouhoNumList = new List<int>();
int[] KouhoNumArr = { 1, 2, 5, 6, 8, 9 };
foreach (int EachNum1 in KouhoNumArr) {
KouhoNumList.Add(EachNum1);
foreach (int EachNum2 in KouhoNumArr) {
KouhoNumList.Add(EachNum1 * 10 + EachNum2);
}
}
return KouhoNumList.ToArray();
}
//枝切り判定を行う
static bool IsEdakiri(JyoutaiDef pWillPush)
{
if (pWillPush.IsAscOmitLast == false) return true;
if (pWillPush.CalcResult > 1996) return true;
if (pWillPush.IsSyokouOver) return true;
if (pWillPush.Is2kouOver) return true;
return false;
}
//解に到達したかを返す
static bool IsAnswer(JyoutaiDef pWillPush)
{
//1996になる式は、必ず3つの数値が必要
//1996を素因数分解すると 2*2*409のため、2桁の数同士の掛け算では作成できないから
if (pWillPush.NumCnt <= 2) return false;
//1996になる式には、必ず+と*がある
if (pWillPush.Expression.Contains("+") == false) return false;
if (pWillPush.Expression.Contains("*") == false) return false;
if (pWillPush.IsAscAll == false) return false;
if (pWillPush.MustKakezanIsOK == false) return false;
//左辺の計算式の計算結果が1996であること
if (pWillPush.CalcResult != 1996) return false;
//逆さまに見た左辺の計算結果が9661であること
string RevExpression = DeriveRevExpression(pWillPush.Expression);
if (CalcExpression(RevExpression) != 9661) return false;
return true;
}
//+でsplitしてから*でsplitして、数式をチェックして状態Defを更新する
static void CheckExpression(ref JyoutaiDef pWillPush)
{
pWillPush.CalcResult = 0;
pWillPush.IsAscAll = pWillPush.IsAscOmitLast = true;
pWillPush.IsSyokouOver = false;
pWillPush.Is2kouOver = false;
pWillPush.MustKakezanIsOK = true;
var KouValList = new List<int>();
bool AppearKakezan = false;
string[] wkArr1 = pWillPush.Expression.Split(new char[] { '+' });
foreach (string EachStr1 in wkArr1) {
string[] wkArr2 = EachStr1.Split(new char[] { '*' });
//掛け算を行った後の、単項の加算がないこと
if (wkArr2.Length >= 2) AppearKakezan = true;
if (AppearKakezan && wkArr2.Length == 1) {
pWillPush.MustKakezanIsOK = false;
}
int ProdVal = 1;
int IntValCurr = 0, IntValPrev;
for (int I = 0; I <= wkArr2.GetUpperBound(0); I++) {
IntValPrev = IntValCurr;
IntValCurr = int.Parse(wkArr2[I]);
if (I >= 1) {
//掛け算の各値が昇順であること
if (IntValCurr < IntValPrev) {
pWillPush.IsAscAll = false;
pWillPush.IsAscOmitLast = false;
return; //処理終了
}
}
ProdVal *= IntValCurr;
}
KouValList.Add(ProdVal);
//最初の項は半分以下でなければならない
if (KouValList.Count == 1 && KouValList[0] > 1996 / 2) {
pWillPush.IsSyokouOver = true;
return; //処理終了
}
//項が2つの場合
if (KouValList.Count == 2) {
if (KouValList[0] + KouValList[1] < 1996) {
if (KouValList[0] + KouValList[1] * 2 > 1996) {
pWillPush.Is2kouOver = true;
return; //処理終了
}
}
}
}
for (int I = 0; I <= KouValList.Count - 1; I++) {
if (I >= 1 && KouValList[I] < KouValList[I - 1])
pWillPush.IsAscAll = false;
if (I >= 1 && I < KouValList.Count - 1 && KouValList[I] < KouValList[I - 1])
pWillPush.IsAscOmitLast = false;
pWillPush.CalcResult += KouValList[I];
}
}
//逆さまに見た文字列を返す
static string DeriveRevExpression(string pExpression)
{
var sb = new System.Text.StringBuilder();
for (int I = pExpression.Length - 1; I >= 0; I--) {
if (pExpression[I] == '9') sb.Append('6');
else if (pExpression[I] == '6') sb.Append('9');
else sb.Append(pExpression[I]);
}
return sb.ToString();
}
//文字列の計算結果を返す
static int CalcExpression(string pExpression)
{
int WillReturn = 0;
string[] wkArr1 = pExpression.ToString().Split(new char[] { '+' });
foreach (string EachStr1 in wkArr1) {
string[] wkArr2 = EachStr1.Split(new char[] { '*' });
int ProdVal = 1;
Array.ForEach(wkArr2, X => ProdVal *= int.Parse(X));
WillReturn += ProdVal;
}
return WillReturn;
}
}
実行結果
省略
経過時間=00:04:44.7141471。2*5+99+12*68を検証中
経過時間=00:05:06.2688903。19+29+59+59を検証中
経過時間=00:05:30.2862645。16+56+68+88を検証中
解1を発見。16+12*22+26*66=1996
逆さまに見たら。9661=99*92+22*21+91
経過時間=00:05:54.3804522。12+9*19+11*18を検証中
経過時間=00:06:19.5641152。11+22*22+9*19を検証中
経過時間=00:06:42.0344211。1*29+91+18*66を検証中
解説
使える数字は、125689だけとなりますので、
これらの数字を組み合わせた2桁までの数の配列を事前に作成してます。