小學數學題,你會嗎?


  一日,某小學生問作業:“將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 ); }
View Code

  運行結果:

輸入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 );   
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM