トップページに戻る
次のC#のサンプルへ
前のC#のサンプルへ
Cマガ電脳クラブ(第145回) 素数団
問題
相異なる10個の数がある。このうちの9個を選んで足し合わせたところ素数となった。
さらにほかのどの9個を選んでもやはり素数となった。
これを満足する10個の数の組み合わせはたくさんあるが,
その中で「10個の数の総和がもっとも小さい組み合わせ」(同じ総和で複数ある場合はすべて)
をお答えいただきたい。
なお、10個の数の総和は素数である必要はない。ここで扱う数はすべて正の整数とする。
ソース
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
const int SumJyougen = 250;
static int[] SosuuArr;
//エラトステネスの篩で上限以下の素数の配列を作成
static void Eratosthenes()
{
var IsSosuuArr = new System.Collections.BitArray(SumJyougen + 1);
for (int I = 2; I <= IsSosuuArr.Count - 1; I++) {
IsSosuuArr[I] = true;
}
for (int I = 2; I * I <= IsSosuuArr.Count - 1; I++) {
if (IsSosuuArr[I] == false) continue;
for (int J = I * 2; J <= IsSosuuArr.Count - 1; J += I) {
IsSosuuArr[J] = false;
}
}
var SosuuList = new List<int>();
for (int I = 2; I <= IsSosuuArr.Count - 1; I++) {
if (IsSosuuArr[I]) SosuuList.Add(I);
}
SosuuArr = SosuuList.ToArray();
}
struct JyoutaiDef
{
internal int CurrVal;
internal List<int> ValList;
}
static void Main()
{
Eratosthenes();
var stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
for (int I = 1; I <= SumJyougen; I += 2) {
WillPush.CurrVal = I;
WillPush.ValList = new List<int>() { I };
bool CanBreak;
if (IsValid(WillPush, out CanBreak))
stk.Push(WillPush);
else if (CanBreak) break;
}
int AnswerSum = int.MaxValue;
while (stk.Count > 0) {
JyoutaiDef Popped = stk.Pop();
//クリア判定
if (Popped.ValList.Count == 10) {
int wkSum = Popped.ValList.Sum();
if (wkSum <= AnswerSum) {
AnswerSum = wkSum;
Console.WriteLine("総和が{0}の解候補を発見", wkSum);
Popped.ValList.ForEach(X => Console.Write("{0},", X));
Console.WriteLine();
}
}
for (int I = Popped.CurrVal + 2; I <= SumJyougen; I += 2) {
WillPush.CurrVal = I;
WillPush.ValList = new List<int>(Popped.ValList) { I };
bool CanBreak;
if (IsValid(WillPush, out CanBreak))
stk.Push(WillPush);
else if (CanBreak) break;
}
}
}
//有効な状態かを返す
static bool IsValid(JyoutaiDef pJyoutaiDef, out bool pCanBreak)
{
pCanBreak = false;
int CurrCnt = pJyoutaiDef.ValList.Count;
int CurrSum = pJyoutaiDef.ValList.Sum();
int RestCnt = 10 - CurrCnt;
int MinRest = 0;
for (int I = 1; I <= RestCnt; I++) {
MinRest += pJyoutaiDef.CurrVal + 2 * I;
}
if (CurrSum + MinRest > SumJyougen) {
pCanBreak = true;
return false;
}
if (CurrCnt == 9) {
if (Array.BinarySearch(SosuuArr, CurrSum) < 0) return false;
}
if (CurrCnt == 10) {
for (int I = 0; I <= pJyoutaiDef.ValList.Count - 1; I++) {
if (Array.BinarySearch(SosuuArr, CurrSum - pJyoutaiDef.ValList[I]) < 0)
return false;
}
}
return true;
}
}
実行結果
総和が242の解候補を発見
1,3,9,13,15,19,31,43,45,63,
総和が200の解候補を発見
1,3,7,9,19,21,27,33,37,43,
解説
下記の必要条件をふまえて、
総和が250以下となる、10個の数を、深さ優先探索で列挙してます。
■■■必要条件1■■■
10個の数の総和が偶数であること
10個の数の総和が奇数と仮定すると
10個の数の中に、少なくとも1つの奇数と含むことになり
その奇数以外の総和が、2より大きい偶数になるから矛盾
■■■必要条件2■■■
10個の数の中に偶数を含まないこと
必要条件1により、10個の数の総和が偶数なので、
10個の数の中に偶数を含んだら
その偶数以外の総和が、2より大きい偶数になるから不適