1. Пт 17 Июль 2015

   # Моё решение задачи 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). Но, к сожалению, на этапе написания кода я про это забыл.

   Категория: Project Euler

   Теги: Project Euler, c++, BGL,