小学数学题,你会吗?

  一日,某小学生问作业:“将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

 

posted @ 2013-07-22 20:22  garbageMan  阅读(2865)  评论(27编辑  收藏  举报