N个数快速计算24点

问题:

    N个1到13之间的自然数,能否通过加减乘除计算(每个数有且只能用一次)得到24?

 

计算24点常用的算法有:① 任取两个数,计算后,将结果放回去,再从剩下的数中任取两个,如此反复直到只剩下一个数;② 先构建前缀/后缀表达式,再计算该表达式;③ 用集合保存中间结果,集合间两两进行合并计算得到新集合(或者对给定的一个集合,对其所有的子集合进行合并计算)。

本文采用第一种方法。定义六种操作符:ADD、SUB、MUL、DIV、RSUB、RDIV,分别对应加、减、乘、除、反减和反除(反减/除:先交换两个数,再减/除)。

显然,取两个数计算时,六种计算结果可能有重复,可以对这6个结果进行去重(实际上,只要分别对加减(ADD、SUB、RSUB)和乘除(MUL、DIV、RDIV)的3个计算结果进行去重判断就可以了,效率和对6个结果去重相差不大)。

另外一种剪枝方法:保存每个数上次计算时所用的操作符(初始值为空)。所取的两个数:

若某个数的上次操作符为减(SUB、RSUB),那么不进行加减(ADD、SUB、RSUB)计算。

若某个数的上次操作符为除(DIV、RDIV),那么不进行乘除(MUL、DIV、RDIV)计算。

比如:取的两个数为 a-b 和 c(c的上次操作符任意),如果进行加减计算的话,

a-b+c 和 c+a-b重复,

c-(a-b)和 c+b-a重复

a-b-c 和 c+b RSUB a重复

也就是说,上次操作符为减的,进行加减计算时,总可以转为某个上次操作符为加的表达式,因而可以不计算。同样,上次操作符为除的,不进行乘除计算。

当然,还可以考虑记录位置进行剪枝,这样避免a+b+c和a+c+b都进行计算。但要注意的是:在给定的组合无解时,越多的剪枝方法,极有可能提高搜索效率,但在给定的组合有解时,很可能反而降低搜索效率。

另外,对有解时输出的表达式的处理对程序的性能影响很大。如果每次计算都保存对应的表达式,会进行大量的字符串操作,严重影响性能。实际上,每次计算只要保存取出的两个数的位置和所进行计算的操作符就够了,最终需要输出表达式时,只要模拟一下递归函数调用过程,进行相应的字符串操作。

下面是程序(gcc 4.5,优化参数-O2)在T4200/2G单核下运行的结果,计算10个数,646646个组合只用了不到13分钟。

N个1到13之间的数的所有组合,计算24点

N

4

5

6

7

8

9

10

时间(ms)

78

610

4157

19593

160922

173766

764328

有解组合数

1362

6104

18554

50386

125969

293930

646646

总组合

1820

6188

18564

50388

125970

293930

646646

总表达式

1124776

15656645

105278906

492587616

3760744504

4535030813

19912345238

表达式A

457549

11864184

88679768

409129896

1173803224

4535030813

19912345238

表达式B

667227

3792461

16599138

83457720

2586941280

0

0

总函数调用

1482939

20950792

141892513

669790534

5258218577

6112529945

26948662625

函数调用A

603206

15849306

119160441

551913059

1576965280

6112529945

26948662625

函数调用B

879733

5101486

22732072

117877475

3681253297

0

0

其中:表达式A/B、函数调用A/B为:给定的n个数有/无解时,所处理的表达式总数和函数调用总次数。

 

N=8 与N=9,两个所用时间只相差13秒,这是由于N为8时,有一个组合(8个1)无解,判断这个组合无解需110多秒,而计算剩下的125969个组合还不要50秒。

 

ExpandedBlockStart.gif N个数计算24
// www.cnblogs.com/flyinghearts
#include < iostream >
#include
< fstream >
#include
< sstream >
#include
< vector >
#include
< ctime >
#include
< cmath >
using  std::cout;
using  std:: string ;
using  std::vector;
using  std::ofstream;
using  std::stringstream;

class  Calc {
 
public :
  Calc(){};
  
void  print_result()  const ;
  
bool  run( const   int  src[], size_t sz,  double  n  =   24.0
           
bool  expr_calc  =   true bool  show  =   true ); 
  
void  calc_range( int  first,  int  last,size_t N  =   4 ,
                 
double  M  =   24 string  filename  =   " 24.out " );
  
const   string &  get_expr()  const  {  return  expr;}
  size_t get_count_expr() 
const  {  return  count_expr;}
  size_t get_count_func() 
const  {  return  count_func;}
  
 
private :
  Calc(
const  Calc & );
  Calc
&   operator = ( const  Calc & );
  
bool  init( const   int  src[], size_t sz,  double  n);
  
bool  calc(size_t step);
  inline 
bool  calc2(size_t step, size_t pos2, double  na,  double  nb,  int  op);
  
void  calc_expr(); 
  
  
void  add_parentheses( string &  str) {
    
string  tmp;  tmp.reserve(str.size()  +   2 );
    tmp 
+=   ' ( ' ;  tmp  +=  str;  tmp  +=   ' ) ' ;
    str.swap(tmp);
  }
  
  
char  get_op_char( int  op) {  return   char (op  >>  RSHIFT); }
  
int  get_opv( int  op) {  return  op  &  OPV_MASK; }
  
  
// 0-2位表示操作符的优先级 加减: 1 乘除2  初始值4
  
// +3位,即RFLAG标志,表示对减除法,交换两个操作数后再计算
  
// 4-7位表示操作符,8-15位表示该操作符的ascii值
   enum  { 
    OP_NULL 
=   4 , RFLAG  =   8 , RSHIFT  =   8 , OPV_MASK  =   7 ,
    FLAG_ADD 
=   0x10 , FLAG_SUB  =   0x20 , FLAG_MUL  =   0x40 , FLAG_DIV  =   0x80 ,
    ADD 
=   ' + '   <<  RSHIFT  |  FLAG_ADD  |   1 , SUB  =   ' - '   <<  RSHIFT  |  FLAG_SUB  |   1 ,
    MUL 
=   ' * '   <<  RSHIFT  |  FLAG_MUL  |   2 , DIV  =   ' / '   <<  RSHIFT  |  FLAG_DIV  |   2 ,
    RSUB 
=  SUB  |  RFLAG, RDIV  =  DIV  |  RFLAG,
  };

  
struct  Info_step {               // 记录每一步取两个数所进行的计算
    size_t first;                  // 第一个操作数位置 
    size_t second;                 // 第二个操作数位置
     int  op;                        // 操作符
  };
  
  size_t size;                    
  
string  expr;                     // 得到的表达式 
   double  result;                   // 要得到的结果值
  size_t count_expr;               // 处理的表达式总数     
  size_t count_func;               // 函数被调用次数
  vector < int >  old_number;          // 要计算的数
  vector < double >  number;           // 中间计算结果           
  vector < int >  ops;                // 上一次计算所用的操作符,初始值要设为OP_NULL 
  vector < Info_step >  info_step;  
};

bool  Calc::init( const   int  src[], size_t sz,  double  n)
{
  
if  (sz  ==   0   ||  src  ==  NULL)  return   false ;
  size 
=  sz;
  expr.clear();
  result 
=  n;
  count_expr 
=   0 ;
  count_func 
=   0 ;
  old_number.assign(src, src 
+  sz); 
  number.assign(src, src
+ sz);
  ops.assign(sz, OP_NULL);
  info_step.clear();
  info_step.resize(sz);
  
return   true ;
}

bool  Calc::run( const   int  src[], size_t sz,  double  n,  bool  expr_calc,  bool  show)
{
  
if  ( !  init(src, sz, n))  return   false ;
  
bool  ret  =  calc(sz);
  
if  (ret  &&  expr_calc) calc_expr();
  
if  (show) print_result(); 
  
return  ret;
}


void  Calc::calc_expr()
{
  
const  size_t sz  =   size;
  
static  vector < string >  str;
  
static  vector < int >  op_prev;
  
static  stringstream ss;
  str.resize(sz);  
  op_prev.assign(sz,OP_NULL);    
// 初始值
   for  (size_t i  =   0 ; i  <  sz;  ++ i) {
    ss 
<<  old_number[i];
    getline(ss, str[i]);
    ss.clear();
  }
  
for  (size_t k  =  sz; k --   >   1 ; ) {
    size_t i 
=  info_step[k].first;
    size_t j 
=  info_step[k].second;
    
int  op  =  info_step[k].op;
    
int  opv =  get_opv(op);
    
int  op1v, op2v;
    
if  (op  &  RFLAG) { 
      str[i].swap(str[j]);
      op1v 
=  get_opv(op_prev[j]);
      op2v 
=  get_opv(op_prev[i]);
    } 
else  {
      op1v 
=  get_opv(op_prev[i]);
      op2v 
=  get_opv(op_prev[j]);
    }
    
    
if  (opv  >  op1v) add_parentheses(str[i]);
    
if  (opv  >  op2v  ||  (opv  ==  op2v  &&  (op  &  (FLAG_SUB  |  FLAG_DIV)))) 
      add_parentheses(str[j]);
    op_prev[i] 
=  op;
    op_prev[j] 
=  op_prev[k];
    str[i].reserve(str[i].size() 
+  str[j].size()  +   1 );
    str[i] 
+=  get_op_char(op);
    str[i] 
+=  str[j]; 
    str[j].swap(str[k]);
  }
  expr.swap(str[
0 ]);
}

bool  Calc::calc(size_t step)
{
  
++ count_func;
  
if  (step  <=   1 ) {
    
++ count_expr;
    
const   double  zero  =  1e - 9 ;   
    
if  (fabs(result  -  number[ 0 ])  <  zero)  return   true
    
return   false ;
  }
  
-- step;
  
for  (size_t i  =   0 ; i  <  step; i ++ ){
    info_step[step].first 
=  i;
    
for (size_t j  =  i  +   1 ; j  <=  step; j ++ ) {
      info_step[step].second 
=  j;
      
double  na  =  number[i];
      
double  nb  =  number[j];
      
int  op1  =  ops[i];
      
int  op2  =  ops[j];
      number[j] 
=  number[step];
      ops[j] 
=  ops[step]; 
      
int  tt  =  op1  |  op2;
      
bool  ba  =   true , bb  =   true ;
      
if  (tt  &  FLAG_SUB) ba  =   false ;
      
if  (tt  &  FLAG_DIV) bb  =   false ;
      
      
if  (ba) {
        
if  (calc2(step, i, na, nb, ADD))  return   true ;
        
if  (nb  !=   0   &&  calc2(step, i, na, nb, SUB))  return   true ;
        
if  (na  !=  nb  &&  na  !=   0   &&  calc2(step, i, na, nb, RSUB))  return   true ;
      }
      
      
if  (bb) {
        
double  nmul  =  na  *  nb;
        
if  (calc2(step, i, na, nb, MUL))  return   true ;
        
if  (na  !=   0   &&  nb  != 0 ) {
          
double  ndiv  =  na  /  nb;
          
if  (nmul  !=  ndiv  &&  calc2(step,i,na, nb, DIV))  return   true
          
double  nrdiv  =  nb  /  na;          
          
if  (nrdiv  !=  ndiv  &&  nrdiv  !=  nmul  &&  calc2(step,i,na, nb, RDIV))
            
return   true ;
        }
      }
      number[i] 
=  na;
      number[j] 
=  nb;
      ops[i] 
=  op1;
      ops[j] 
=  op2;
    }
  }
  
return   false ;
}

inline 
bool  Calc::calc2(size_t step, size_t pos2, double  na, double  nb,  int  op)
{
  info_step[step].op 
=  op;
  ops[pos2] 
=  op;
  
switch  (op) {
    
case  ADD:   number[pos2]  =  na  +  nb;  break ;
    
case  SUB:   number[pos2]  =  na  -  nb;  break ;
    
case  MUL:   number[pos2]  =  na  *  nb;  break ;
    
case  DIV:   number[pos2]  =  na  /  nb;  break ;
    
case  RSUB:  number[pos2]  =  nb  -  na;  break ;
    
case  RDIV:  number[pos2]  =  nb  /  na;  break ;
    
default  :  break ;
  }
  
return  calc(step);
}

void  Calc::print_result()  const
{
  
if  (old_number.empty())  return ;
  cout 
<<   " number:  " ;
  
for  (size_t i  =   0 ; i  <  old_number.size();  ++ i) 
    cout 
<<  old_number[i]  <<   "   " ;
  cout 
<<   " \n " ;
  
if  ( !  expr.empty()) std::cout  <<   " result:  "   <<  expr  <<   " = "   <<  result  <<   " \n " ;
  cout 
<<   " expr/func:  "   <<  count_expr  <<   " / "   <<  count_func  <<   " \n\n " ;
}


void  Calc::calc_range( int  first,  int  last,size_t N,  double  M,  string  filename)
{
  
if  (N  == 0   ||  first  >=  last  ||  filename.empty())  return ;
  clock_t ta 
=  clock();
  vector
< int >  vv(N, first);
  
int   * end  =   & vv[N - 1 ],  * =  end,  * arr  =   & vv[ 0 ];
  ofstream ofs(filename.c_str());
  size_t count 
=   0 ;
  size_t count_b 
=   0 ;
  typedef unsigned 
long   long  size_tt;
  size_tt count_expr_a 
=   0 ;
  size_tt count_expr_b 
=   0 ;
  size_tt count_func_a 
=   0 ;
  size_tt count_func_b 
=   0 ;
  
while ( true ){
    
++ count_b;
    
if  (run(arr,N,M, true , false )) {
      ofs.width(
4 );
      ofs
<<    ++ count  <<   "      " ;
      
for  (size_t i  =   0 ; i  <  N;  ++ i) { 
        ofs.width(
2 );        
        ofs 
<<  arr[i] <<   "   " ;
      }  
      ofs
<<   "      "   <<  get_expr()  <<   " \n " ;
      count_expr_a 
+=  count_expr;     
      count_func_a 
+=  count_func; 
    } 
else  {
      count_expr_b 
+=  count_expr;     
      count_func_b 
+=  count_func;
    }    
    
while  (p  >=  arr  &&   * >=  last)  -- p;
    
if  (p  <  arr)  break ;
    
int  tmp  =   ++* p;
    
while  (p  <  end)  *++ =  tmp;
  }
  ofs.close();
  
const   char  sep[]  =   " / " ;
  cout 
<<   " N:  "   <<  N  <<   "     M:  "   <<  M 
       
<<   "        range:  "   <<  first  <<   " - "   <<  last  <<   " \n "   
       
<<   " expr:   "   <<  count_expr_a  +  count_expr_b  <<  sep  <<   count_expr_a 
       
<<  sep  <<  count_expr_b  <<   " \n "
       
<<   " func:   "   <<  count_func_a  +  count_func_b  <<  sep  <<   count_func_a 
       
<<  sep  <<  count_func_b  <<   " \n "  
       
<<   " total:  "   <<  count  <<  sep  <<  count_b  <<   " \n "
       
<<   " time:   "   <<  clock()  -  ta  <<   "   ms\n\n "   <<  std::flush; 
}


int  main()
{
  Calc calc;
  
int  ra[ 4 ] = { 3 , 3 , 8 , 8 };
  
int  rb[ 4 ] = { 5 , 7 , 7 , 11 };
  
int  rc[ 4 ] = { 4 , 7 , 11 , 13 };
  calc.run(ra,
4 );
  calc.run(rb,
4 );
  calc.run(rc,
4 );
  
// calc.calc_range(1,13,4,24,"nul");
  
// calc.calc_range(1,50,4,24,"nul");
  
// calc.calc_range(1,100,4,24,"nul");
  calc.calc_range( 1 , 13 4 , 24 , " a24-04t.txt " );
  calc.calc_range(
1 , 13 5 , 24 , " a24-05t.txt " );
  calc.calc_range(
1 , 13 6 , 24 , " a24-06t.txt " );
  calc.calc_range(
1 , 13 7 , 24 , " a24-07t.txt " );
  calc.calc_range(
1 , 13 8 , 24 , " a24-08t.txt " );
  calc.calc_range(
1 , 13 9 , 24 , " a24-09t.txt " );
  calc.calc_range(
1 , 13 , 10 , 24 , " a24-10t.txt " );
}


 

转载于:https://www.cnblogs.com/flyinghearts/archive/2011/01/08/1930918.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值