C #, 10039 28164 dígitos
6 0{28157} 169669
Editar: Hice otro programa basado en el algoritmo de Qualtagh con algunas modificaciones menores:
- Estoy buscando los números de la forma L000 ... 000R, donde L es compuesto izquierdo, R es compuesto derecho. Permití que el número compuesto izquierdo L tuviera varios dígitos, aunque esto es principalmente un cambio estilístico, y probablemente no afecta la eficiencia del algoritmo.
- He agregado subprocesos múltiples para acelerar la búsqueda.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Mpir.NET;
class Program
{
const int PrimeNotFound = int.MaxValue;
private static BitArray _primeSieve;
private static HashSet<Tuple<int, int>> _templatesToSkip = new HashSet<Tuple<int, int>>();
static void Main(string[] args)
{
int bestDigitCount = 0;
foreach (Tuple<int, int> template in GetTemplates())
{
int left = template.Item1;
int right = template.Item2;
if (SkipTemplate(left, right))
continue;
int zeroCount = GetZeroCountOfPrime(left, right);
if (zeroCount != PrimeNotFound)
{
int digitCount = left.ToString().Length + right.ToString().Length + zeroCount;
if (digitCount >= bestDigitCount)
{
string primeStr = left + " 0{" + zeroCount + "} " + right;
Console.WriteLine("testing " + primeStr);
bool isFragile = IsFragile(left, right, zeroCount);
Console.WriteLine(primeStr + " is fragile: " + isFragile);
if (isFragile)
bestDigitCount = digitCount;
}
_templatesToSkip.Add(template);
}
}
}
private static int GetZeroCountOfPrime(int left, int right)
{
_zeroCount = 0;
int threadCount = Environment.ProcessorCount;
Task<int>[] tasks = new Task<int>[threadCount];
for (int i = 0; i < threadCount; i++)
tasks[i] = Task.Run(() => InternalGetZeroCountOfPrime(left, right));
Task.WaitAll(tasks);
return tasks.Min(task => task.Result);
}
private static int _zeroCount;
private static int InternalGetZeroCountOfPrime(int left, int right)
{
const int maxZeroCount = 40000;
int zeroCount = Interlocked.Increment(ref _zeroCount);
while (zeroCount <= maxZeroCount)
{
if (zeroCount % 1000 == 0)
Console.WriteLine("testing " + left + " 0{" + zeroCount + "} " + right);
if (IsPrime(left, right, zeroCount))
{
Interlocked.Add(ref _zeroCount, maxZeroCount);
return zeroCount;
}
else
zeroCount = Interlocked.Increment(ref _zeroCount);
}
return PrimeNotFound;
}
private static bool SkipTemplate(int left, int right)
{
for (int leftDiv = 1; leftDiv <= left; leftDiv *= 10)
for (int rightDiv = 1; rightDiv <= right; rightDiv *= 10)
if (_templatesToSkip.Contains(Tuple.Create(left / leftDiv, right % (rightDiv * 10))))
return true;
return false;
}
private static bool IsPrime(int left, int right, int zeroCount)
{
return IsPrime(left.ToString() + new string('0', zeroCount) + right.ToString());
}
private static bool IsPrime(string left, string right, int zeroCount)
{
return IsPrime(left + new string('0', zeroCount) + right);
}
private static bool IsPrime(string s)
{
using (mpz_t n = new mpz_t(s))
{
return n.IsProbablyPrimeRabinMiller(20);
}
}
private static bool IsFragile(int left, int right, int zeroCount)
{
string leftStr = left.ToString();
string rightStr = right.ToString();
for (int startIndex = 0; startIndex < leftStr.Length - 1; startIndex++)
for (int count = 1; count < leftStr.Length - startIndex; count++)
if (IsPrime(leftStr.Remove(startIndex, count), rightStr, zeroCount))
return false;
for (int startIndex = 1; startIndex < rightStr.Length; startIndex++)
for (int count = 1; count <= rightStr.Length - startIndex; count++)
if (IsPrime(leftStr, rightStr.Remove(startIndex, count), zeroCount))
return false;
return true;
}
private static IEnumerable<Tuple<int, int>> GetTemplates()
{
const int maxDigitCount = 8;
PreparePrimeSieve((int)BigInteger.Pow(10, maxDigitCount));
for (int digitCount = 2; digitCount <= maxDigitCount; digitCount++)
{
for (int leftCount = 1; leftCount < digitCount; leftCount++)
{
int rightCount = digitCount - leftCount;
int maxLeft = (int)BigInteger.Pow(10, leftCount);
int maxRight = (int)BigInteger.Pow(10, rightCount);
for (int left = maxLeft / 10; left < maxLeft; left++)
for (int right = maxRight / 10; right < maxRight; right++)
if (IsValidTemplate(left, right, leftCount, rightCount))
yield return Tuple.Create(left, right);
}
}
}
private static void PreparePrimeSieve(int limit)
{
_primeSieve = new BitArray(limit + 1, true);
_primeSieve[0] = false;
_primeSieve[1] = false;
for (int i = 2; i * i <= limit; i++)
if (_primeSieve[i])
for (int j = i * i; j <= limit; j += i)
_primeSieve[j] = false;
}
private static bool IsValidTemplate(int left, int right, int leftCount, int rightCount)
{
int rightDigit = right % 10;
if ((rightDigit != 1) && (rightDigit != 9))
return false;
if (left % 10 == 0)
return false;
if ((left + right) % 3 == 0)
return false;
if (!Coprime(left, right))
return false;
int leftDiv = 1;
for (int i = 0; i <= leftCount; i++)
{
int rightDiv = 1;
for (int j = 0; j <= rightCount; j++)
{
int combination = left / leftDiv * rightDiv + right % rightDiv;
if (_primeSieve[combination])
return false;
rightDiv *= 10;
}
leftDiv *= 10;
}
return true;
}
private static bool Coprime(int a, int b)
{
while (b != 0)
{
int t = b;
b = a % b;
a = t;
}
return a == 1;
}
}
Vieja respuesta:
8 0{5436} 4 0{4600} 1
Hay algunos patrones notables para números primos frágiles:
600..00X00..009
900..00X00..009
800..00X00..001
999..99X99..999
donde X puede ser 1, 2, 4, 5, 7 u 8.
Para tales números solo tenemos que considerar (longitud - 1) posibles Remove
operaciones. Las otras Remove
operaciones producen números duplicados o obviamente compuestos. Traté de buscar todos esos números con hasta 800 dígitos y noté que aparecen 4 patrones con más frecuencia que el resto: 8007001, 8004001, 9997999 y 6004009. Dado que Emil y Jakube están usando el patrón 999X999, decidí usar 8004001 solo Para agregar algo de variedad.
He agregado las siguientes optimizaciones al algoritmo:
- Empiezo a buscar desde números con 7000 dígitos y luego incremento la longitud en 1500 cada vez que se encuentra una prima frágil. Si no hay un número primo frágil con una longitud dada, entonces lo incremento en 1. 7000 y 1500 son solo números arbitrarios que parecían apropiados.
- Estoy usando subprocesos múltiples para buscar los números con diferente longitud al mismo tiempo.
- El resultado de cada verificación principal se almacena en una tabla hash para evitar verificaciones duplicadas.
- Estoy usando la implementación Miller-Rabin de Mpir.NET , que es muy rápida (MPIR es una bifurcación de GMP).
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Mpir.NET;
class Program
{
const string _template = "8041";
private static ConcurrentDictionary<Tuple<int, int>, byte> _compositeNumbers = new ConcurrentDictionary<Tuple<int, int>, byte>();
private static ConcurrentDictionary<int, int> _leftPrimes = new ConcurrentDictionary<int, int>();
private static ConcurrentDictionary<int, int> _rightPrimes = new ConcurrentDictionary<int, int>();
static void Main(string[] args)
{
int threadCount = Environment.ProcessorCount;
Task[] tasks = new Task[threadCount];
for (int i = 0; i < threadCount; i++)
{
int index = i;
tasks[index] = Task.Run(() => SearchFragilePrimes());
}
Task.WaitAll(tasks);
}
private const int _lengthIncrement = 1500;
private static int _length = 7000;
private static object _lengthLock = new object();
private static object _consoleLock = new object();
private static void SearchFragilePrimes()
{
int length;
lock (_lengthLock)
{
_length++;
length = _length;
}
while (true)
{
lock (_consoleLock)
{
Console.WriteLine("{0:T}: length = {1}", DateTime.Now, length);
}
bool found = false;
for (int rightCount = 1; rightCount <= length - 2; rightCount++)
{
int leftCount = length - rightCount - 1;
if (IsFragilePrime(leftCount, rightCount))
{
lock (_consoleLock)
{
Console.WriteLine("{0:T}: {1} {2}{{{3}}} {4} {2}{{{5}}} {6}",
DateTime.Now, _template[0], _template[1], leftCount - 1,
_template[2], rightCount - 1, _template[3]);
}
found = true;
break;
}
}
lock (_lengthLock)
{
if (found && (_length < length + _lengthIncrement / 2))
_length += _lengthIncrement;
else
_length++;
length = _length;
}
}
}
private static bool IsFragilePrime(int leftCount, int rightCount)
{
int count;
if (_leftPrimes.TryGetValue(leftCount, out count))
if (count < rightCount)
return false;
if (_rightPrimes.TryGetValue(rightCount, out count))
if (count < leftCount)
return false;
if (!IsPrime(leftCount, rightCount))
return false;
for (int i = 0; i < leftCount; i++)
if (IsPrime(i, rightCount))
return false;
for (int i = 0; i < rightCount; i++)
if (IsPrime(leftCount, i))
return false;
return true;
}
private static bool IsPrime(int leftCount, int rightCount)
{
Tuple<int, int> tuple = Tuple.Create(leftCount, rightCount);
if (_compositeNumbers.ContainsKey(tuple))
return false;
using (mpz_t n = new mpz_t(BuildStr(leftCount, rightCount)))
{
bool result = n.IsProbablyPrimeRabinMiller(20);
if (result)
{
_leftPrimes.TryAdd(leftCount, rightCount);
_rightPrimes.TryAdd(rightCount, leftCount);
}
else
_compositeNumbers.TryAdd(tuple, 0);
return result;
}
}
private static string BuildStr(int leftCount, int rightCount)
{
char[] chars = new char[leftCount + rightCount + 1];
for (int i = 0; i < chars.Length; i++)
chars[i] = _template[1];
chars[0] = _template[0];
chars[leftCount + rightCount] = _template[3];
chars[leftCount] = _template[2];
return new string(chars);
}
}