Моё решение задачи 60
Необходимо найти множество из пяти простых чисел с минимальной суммой такое, что после “склеивания” в любом порядке любых двух чисел из него тоже будет простое число. Здесь под процедурой “склеивания” чисел \(a\) и \(b\) подразумевается получения из \(a = \overline{a_1 a_2 \ldots a_n}\) и \(b = \overline{b_1 b_2 \ldots b_m}\) некоторого \(c\) так, что \(c = \overline{a_1 a_2 \ldots a_n b_1 b_2 \ldots b_m}\).
Полное условие можно найти тут
Для начала, можно понять, что непосредственный перебор “в лоб” слишком медленный и нужного результата не даст. Поэтому хочется уйти от, как мне кажется, не самого формализуемого условия к чему-то более простого, с чем проще работать. Давайте сначала поймём, какие вообще числа могут быть в одном множестве. Для этого достаточно перебрать все разбиения на два подчисла всех простых чисел. Это достаточно быстро, порядка \(O\left( N \right)\) операций. Важно не забыть, что мы можем разбивать число \(p\) только на \(\overline{p_1 p_2}\), между числами не может быть нулей! То есть если число 37 разбивается на 3 и 7, то 307 нет.
Пусть мы получили набор таких разбиений, то есть набор пар вида \(\left( p, q \right)\). Давайте составим из них граф, где вершинки это простые числа, а ориентированное ребро из \(p\) в \(q\) означает, что есть пара \(\left( p, q \right)\). Из того, что порядок склеивания чисел произвольный сразу следует, что рассматриваемый граф должен быть неориентированным. Таким образом, все пары \(\left( p, q \right)\) для которых нет пары \(\left( q, p \right)\) необходимо выкинуть, а из оставшихся построить граф.
Теперь задача стало гораздо понятнее: достаточно выбрать клику размера 5, что сумма значений её вершин минимальна. В общем случае, это достаточно ресурсоёмкая(как мне кажется) задача, но в реальном графе количество рёбер не слишком большое. В худшем случае, по теореме Турана, количество рёбер в графе лишь с одной такой кликой примерно на 10% меньше числа рёбер в полном графе.
Непосредственно сам поиск такой клики можно реализовать тривиально. Ниже мой код на C++11 с использованием библиотеки Boost Graph Library (BGL).
#include <iostream>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <cstdint>
#include <boost/graph/adjacency_list.hpp>
#include "primesieve.hpp"
using namespace std;
using namespace boost;
typedef adjacency_list<listS, vecS, undirectedS> Graph;
template <class T>
vector< T > getDigs(T num) {
vector< T > res;
while ( num != 0 ) {
res.push_back(num % 10);
num = num / 10;
}
reverse(res.begin(),res.end());
return res;
}
template <class T>
T getNum(const vector<T> &digs){
T res(0);
T cur_mult(1);
for (auto it = digs.rbegin(); it != digs.rend(); ++it){
res += *it * cur_mult;
cur_mult *= T(10);
}
return res;
}
template <class T>
vector< pair<T,T> > getPrimePairs(T prime, set<T> &primes) {
vector< pair<T,T> > res;
auto digs = getDigs<T>(prime);
for (size_t i = 1; i <= digs.size() - 1; ++i){
T q1 = getNum(vector<T>(digs.begin(), digs.begin() + i));
T q2 = getNum(vector<T>(digs.begin() + i, digs.end()));
if ( (primes.find(q1) != primes.end())
&& (primes.find(q2) != primes.end()) && (digs.at(i) != 0) ){
res.push_back(make_pair(q1,q2));
}
}
return res;
}
// removes all (p,q) that (p,q) in edges but (q,p) is not
template <class T>
void delBadEdges(vector< pair<T,T> > &edges){
map< int64_t, int > mults; // contains counts of p * q
for (auto p: edges) {
int64_t key = p.first * p.second;
if ( mults.find(key) != mults.end() )
mults[key] += 1;
else
mults[key] = 1;
}
vector< pair<T,T> > res;
for (auto p: edges) {
int64_t key = p.first * p.second;
if ( mults[key] == 2 ){
res.push_back(p);
// edge (p,q) is already exists
mults[key] = 0;
}
}
swap(res, edges);
}
template <class T>
set<T> getUniqNums(vector< pair<T,T> > &edges){
set<T> res;
for (auto p: edges) {
res.insert(p.first);
res.insert(p.second);
}
return res;
}
template <class T>
void getNeedSet(Graph &g, Graph::vertex_descriptor st,int depth,
set<Graph::vertex_descriptor> cur_set, const map<Graph::vertex_descriptor, T> &vert_prime,
vector< set<Graph::vertex_descriptor> > &v_out){
auto edges = out_edges(st, g);
vector< Graph::vertex_descriptor > adj_verts;
for (auto e_it = edges.first; e_it != edges.second; ++e_it)
adj_verts.push_back(target(*e_it,g));
for (auto prev_v: cur_set)
if ( find(adj_verts.begin(), adj_verts.end(), prev_v) == adj_verts.end() )
return;
cur_set.insert(st);
if ( depth == 1 ){
v_out.push_back(cur_set);
return;
}
for (auto v: adj_verts){
if ( (vert_prime.at(v) < vert_prime.at(st)) )
continue;
getNeedSet(g,v,depth - 1, cur_set, vert_prime, v_out);
}
}
int main(int argc, char **argv) {
const int MAX_VAL = 100000000;
vector< long > v_primes;
primesieve::generate_primes(MAX_VAL, &v_primes);
set< long > s_primes(v_primes.begin(), v_primes.end());
cout << "Number of primes is " << v_primes.size() << endl;
v_primes.clear();
vector< pair< long,long > > all_edges;
for (auto pr: s_primes){
auto t_v = getPrimePairs(pr, s_primes);
copy(t_v.begin(),t_v.end(),back_inserter(all_edges));
}
cout << "There are " << all_edges.size() << " edges" << endl;
delBadEdges(all_edges);
cout << "After deleting bad edges: " << all_edges.size() << " edges" << endl;
// We only need this primes
s_primes = getUniqNums(all_edges);
map< Graph::vertex_descriptor, long > vert_prime;
map< long, Graph::vertex_descriptor > prime_vert;
Graph g;
for (auto pr: s_primes){
vert_prime[add_vertex(g)] = pr;
}
for (auto v: vert_prime){
prime_vert[v.second] = v.first;
}
for (auto p: all_edges)
add_edge(prime_vert.at(p.first),prime_vert.at(p.second),g);
auto all_vertices = vertices(g);
vector< set<Graph::vertex_descriptor> > goods;
for (auto v_it = all_vertices.first; v_it != all_vertices.second; ++v_it){
getNeedSet(g, *v_it,5, set<Graph::vertex_descriptor>(), vert_prime, goods);
}
vector< pair<size_t, size_t> > good_sums;
for (size_t i = 0; i < goods.size(); ++i){
cout << "Good set #" << i+1 << ": ";
size_t cur_sum = 0;
for (auto el: goods[i]){
el = vert_prime.at(el);
cur_sum += el;
cout << el << " ";
}
cout << endl;
good_sums.push_back(make_pair(cur_sum, i));
}
if ( !good_sums.empty() )
cout << "Result is " << min_element(good_sums.begin(),good_sums.end(), [](pair<size_t, size_t> a, pair<size_t, size_t> b){return a.first < b.first;})->first << endl;
return 0;
}
Ответ: 26033
UPD. Кажется, getNeedSet
можно было реализовать гораздо проще, использую BGL посетителей (Visitors). Но, к сожалению, на этапе написания кода я про это забыл.