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秒。

 

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 = truebool show = true); 
  
void calc_range(int first, int last,size_t N = 4,
                 
double M = 24string 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,134,24,"a24-04t.txt");
  calc.calc_range(
1,135,24,"a24-05t.txt");
  calc.calc_range(
1,136,24,"a24-06t.txt");
  calc.calc_range(
1,137,24,"a24-07t.txt");
  calc.calc_range(
1,138,24,"a24-08t.txt");
  calc.calc_range(
1,139,24,"a24-09t.txt");
  calc.calc_range(
1,13,10,24,"a24-10t.txt");
}


 

posted @ 2011-01-08 21:15  flyinghearts  阅读(4591)  评论(1编辑  收藏  举报