一日,某小學生問作業:“將16分解為若干素數的和,求這些素數積的最大值”。不禁被嚇了一跳。怎么小學生的數學題變得這么難了?
細細詢問,小學生沒學不等式,沒學數學歸納法……。那么只能用最笨的辦法——窮舉,一個個地試的辦法來解決。
窮舉之道,在於一一舉來,不多不少;而不多不少,則在於有條有理,從容不亂。
小於16的素數依次為:2,3,5,7,11,13。顯然,最大積是16和{2,3,5,7,11,13}的函數,將這個最大積記為
F(16,{2,3,5,7,11,13})
該最大積中可能有素因子2也可能沒有,因此
F(16,{2,3,5,7,11,13}) =
MAX (
2 * F(14 ,{2,3,5,7,11,13}) ,
F(16 ,{3,5,7,11,13} ) ,
)
同理,
F(14,{2,3,5,7,11,13}) =
MAX (
2 * F(12 ,{2,3,5,7,11,13}) ,
F(14 ,{3,5,7,11,13} ) ,
)
F(16,{3,5,7,11,13}) =
MAX (
3 * F(3 ,{2,3,5,7,11,13}) ,
F(16 ,{5,7,11,13} ) ,
)
……
由此不難看出這構成了一個遞歸過程,終止的條件為F(n,{})中的素數集合為空或n<=0。
下面用C語言描述這個過程。
用程序解決問題顯然不應該只解決分解16這樣單獨的問題,而應該至少能解決一類問題。為此將問題描述為:
將正整數n分解為若干素數的和,求這些素數積的最大值。
#include <stdio.h> void input( unsigned * ); unsigned maxmul( unsigned , 素數集合類型 ); unsigned maxmul_( unsigned , 素數集合類型 ); unsigned max( unsigned , unsigned ); int main( void ) { unsigned n ; 素數集合類型 素數集合; //這里需要一個素數集合; input( &n ); //輸入n printf("%u\n", maxmul( n , 素數集合 ) ); //輸出最大積 return 0; } unsigned max( unsigned u1 , unsigned u2 ) { return u1 > u2 ? u1 : u2 ; } unsigned maxmul_( unsigned n , 素數集合類型 素數集合 ) { if ( 素數集合為空 || n < 素數集合中的最小元素 ) { return 0; } if ( n == 素數集合中的某個元素 ) { return n; } return max ( 素數集合中的某元素 * maxmul_( n - 素數集合中的某元素 , 素數集合 ) , maxmul_( n , 素數集合刪掉一個元素 ) ); } unsigned maxmul( unsigned n , 素數集合類型 素數集合 ) { if ( n < 4u ) // 小於4的情況無解 return 0; return maxmul_( n , 素數集合 ); } void input( unsigned * p ) { puts( "輸入n:" ); scanf( "%u" , p ); }
至此,還需要給出不大於n的素數集合。由於不清楚不大於n有多少素數,所以用數組表示這個集合顯然不現實,即使用C99的VLA也不夠好。
那么只好用鏈表。問題就成了給出不大於正整數n的素數鏈表。鏈表用下面的數據結構描述:
typedef struct prime_list { unsigned prime; struct prime_list * next; } * P_LIST;
在main()中定義這個鏈表:
P_LIST pr_list = NULL ;
根據n求得這個鏈表
pr_list = build( n );
令我沒想到的是這個函數不那么容易寫,稍不留神就錯。你們體會下!
typedef struct prime_list { unsigned prime; struct prime_list * next; } * P_LIST; typedef enum { NO , YES, } YESNO ; P_LIST build( unsigned ); void build_( P_LIST * , P_LIST * , unsigned , unsigned ); YESNO be_prime( unsigned , P_LIST ); void add ( P_LIST * * , unsigned ) ; void my_malloc( P_LIST * ); void my_malloc( P_LIST * pp ) { if ( ( * pp = malloc( sizeof (* * pp) )) == NULL ) exit(1); } void add ( P_LIST * * ppp_e, unsigned const num ) { my_malloc( * ppp_e ); ( * * ppp_e ) -> prime = num ; ( * * ppp_e ) -> next = NULL; * ppp_e = & ( * * ppp_e ) -> next ; } YESNO be_prime( unsigned n , P_LIST p ) { if ( n == 2u || p == NULL ) { return YES ; } if ( n % p -> prime == 0u ) { return NO ; } return be_prime( n , p -> next ); } void build_( P_LIST * pp_b , P_LIST * pp_e , unsigned num , unsigned n ) { if( num > n ) { return ; } if ( be_prime( num , *pp_b ) == YES ) { add ( &pp_e , num ) ; //將num加入鏈表 } build_( pp_b , pp_e , num + 1u , n ) ; } P_LIST build( unsigned n )//建立不大於n的有序素數鏈表 { P_LIST head = NULL ; build_( &head , &head , 2u , n ); //從2開始 return head; }
最后是完整的代碼。
#include <stdio.h> typedef struct prime_list { unsigned prime; struct prime_list * next; } * P_LIST; typedef enum { NO , YES, } YESNO ; void input( unsigned * ); P_LIST build( unsigned ); void build_( P_LIST * , P_LIST * , unsigned , unsigned ); YESNO be_prime( unsigned , P_LIST ); void add ( P_LIST * * , unsigned ) ; void my_malloc( P_LIST * ); unsigned maxmul( unsigned , P_LIST ); unsigned maxmul_( unsigned , P_LIST ); unsigned max( unsigned , unsigned ); void my_free( P_LIST ); int main( void ) { unsigned n ; P_LIST pr_list = NULL ; input( &n ); //輸入n
pr_list = build( n ); //准備素數表
printf("%u\n", maxmul( n , pr_list ) ); //輸出
my_free( pr_list ); return 0; } void my_free( P_LIST p ) { if ( p != NULL ) { free( p -> next ); free( p ); } } unsigned max( unsigned u1 , unsigned u2 ) { return u1 > u2 ? u1 : u2 ; } unsigned maxmul_( unsigned n , P_LIST p ) { if ( p == NULL || n < p->prime ) { return 0; } if ( n == p->prime ) { return n; } return max ( p -> prime * maxmul_( n - p->prime , p ) , maxmul_( n , p -> next ) ); } unsigned maxmul( unsigned n , P_LIST p ) { if ( n < 4u ) return 0; return maxmul_( n , p ); } void my_malloc( P_LIST * pp ) { if ( ( * pp = malloc( sizeof (* * pp) )) == NULL ) exit(1); } void add ( P_LIST * * ppp_e, unsigned const num ) { my_malloc( * ppp_e ); ( * * ppp_e ) -> prime = num ; ( * * ppp_e ) -> next = NULL; * ppp_e = & ( * * ppp_e ) -> next ; } YESNO be_prime( unsigned n , P_LIST p ) { if ( n == 2u || p == NULL ) { return YES ; } if ( n % p -> prime == 0u ) { return NO ; } return be_prime( n , p -> next ); } void build_( P_LIST * pp_b , P_LIST * pp_e , unsigned num , unsigned n ) { if( num > n ) { return ; } if ( be_prime( num , *pp_b ) == YES ) { add ( &pp_e , num ) ; //將num加入鏈表
} build_( pp_b , pp_e , num + 1u , n ) ; } P_LIST build( unsigned n )//建立不大於n的有序素數鏈表
{ P_LIST head = NULL ; build_( &head , &head , 2u , n ); //從2開始
return head; } void input( unsigned * p ) { puts( "輸入n:" ); scanf( "%u" , p ); }
運行結果:
輸入n:
16
324
題外話:
從數學的角度看,這個題目並不難。只要運用初中數學知識,就不難分析出,對於大於3的正整數n的最大素數積,當n為
6k型正整數時,分為2k個3積最大;
6k+1型正整數時,分為2k-1個3、2個2積最大;
6k+2型正整數時,分為2k個3、1個2積最大;
6k+3型正整數時,分為2k+1個3積最大;
6k+4型正整數時,分為2k個3、2個2積最大;
6k+5型正整數時,分為2k+1個3、1個2積最大。
結論用數學歸納法很容易證明。參見http://bbs.chinaunix.net/thread-4088334-2-1.html
【補記】
建立素數鏈表部分寫得很復雜。今天突然想到原因之一是建立的是有序表,但“有序”在這里其實是不必要的。如果建立的是一個逆序鏈表,代碼要簡單很多。
2013,7,23
//【題目:將16分解為若干素數的和,求這些素數積的最大值】 //用逆序素數表 #include <stdio.h> typedef struct prime_list { int prime; struct prime_list * next; } * P_LIST; typedef enum { NO , YES, } YESNO ; void input( int * ); void build( P_LIST * , int ); YESNO be_prime( int , P_LIST ); void add( P_LIST * , int ); void my_malloc( P_LIST * ); void my_free( P_LIST ); int maxmul( int , P_LIST ); int maxmul_( int , P_LIST ); int max( int , int ); #if 0 //測試 void out( P_LIST ); void out( P_LIST p ) { while ( p != NULL ) { printf("%d ",p->prime); p = p -> next ; } putchar('\n'); } #endif int main( void ) { int n ; P_LIST pr_list = NULL ; input( &n ); //輸入n build( & pr_list , n ); //建立不大於n的素數表 //out( pr_list ); //測試 printf("%d\n", maxmul( n , pr_list ) ); //輸出 my_free( pr_list ); return 0; } int max( int n1 , int n2 ) { return n1 > n2 ? n1 : n2 ; } int maxmul( int n , P_LIST p ) { if ( n < 4 ) return 0; return maxmul_( n , p ) ; } int maxmul_( int n , P_LIST p ) { if ( n < 0 ) return 0; if ( n == 0 ) return 1; if ( p == NULL ) return 0; if ( n < p->prime ) return maxmul_( n , p->next ) ; return max( p->prime * maxmul_( n - p->prime , p ), maxmul_( n , p->next ) ); } void my_free( P_LIST p ) { P_LIST tmp ; while ( ( tmp = p ) != NULL ) { p = p->next; free( tmp ); } } void my_malloc( P_LIST * p_p ) { if ( ( * p_p = malloc( sizeof (* * p_p) )) == NULL ) exit(1); } void add( P_LIST * p_p , int n ) { P_LIST tmp ; my_malloc( &tmp ); tmp->prime= n ; tmp->next = * p_p ; * p_p = tmp ; } YESNO be_prime( int n , P_LIST p) { while ( p != NULL ) { if ( n % p->prime == 0 ) { return NO; } p = p->next ; } return YES; } void build( P_LIST * p_p , int n ) { int i ; for ( i = 2 ; i <= n ; i++ ) { if ( be_prime( i , *p_p ) == YES ) //如果i是素數 { add( p_p , i ); //將i加入鏈表 } } } void input( int * p ) { puts( "輸入n:" ); scanf( "%d" , p ); }

