每日一题(13)——24点 (分治&递归)

问题描述:

    给玩家4张牌,每张面值1~13,采用+、-、*、/以及括号,使其最终结果为24

解答:

1.穷举法

每个数只能使用一次,所以对4个数进行全排列共有4!=24种排列;

需要3个四则运算符号:4^3=64种;

加括号方式:(A(B(CD))), ((A(BC))D), ((AB)(CD)), (A((BC)D))), (((AB)C)D) 5种

共有:4! * 4^3 * 5 = 7680种;

 

2.分治法

假定集合A={1,2,3,4},首先任意取出两个数,如去除1,2,A=A - {1,2},进行不同的四则运算,1+2=3,1-2=-1, 1*2=2, 1/2=0.5, 将结果再分别加入集合A,可得:

B={3,3,4}, C={-1,3,4}, D={2,3,4}, E={0.5,3,4}四个新的集合:

集合A所有可能的值 F(A) = F(B)+F(C)+F(D)+F(E) 计算规模由4个数降低为3个数

伪代码:

  1. F(Array)  
  2.   
  3. {  
  4.   
  5.   if(Array.Length<2)  
  6.   
  7.   {  
  8.   
  9.     if(最终结果为24) 输出表达式;  
  10.   
  11.     else 无法构造   
  12.   
  13.   }  
  14.   
  15.   for each(从数组中任意取两个数的组合)  
  16.   
  17.   {  
  18.   
  19.       for each(运算符(+, -, *,  /))  
  20.   
  21.       {  
  22.   
  23.          1.计算该组合在此运算符下的结果;  
  24.   
  25.          2.将组合中的两个数从原数组中移除,并将步骤1的结果放入数组;  
  26.   
  27.          3.对新数组递归调用f,找到一个表达式则返回;  
  28.   
  29.          4.将步骤1的结果移除,并将该组合中的两个数重新放回该数组;  
  30.   
  31.        }  
  32.   
  33.   }  
  34.   
  35. }  


 

 

3.采用分支限界法求解

 任然假定假定集合A,首先采用分治思想,将集合A划分为A1与A-A1,分别对他们俩进行四则运算求解F(A1)和F(A-A1),最后对这两个集合里的元素进行四则运算,所得到的所有集合的并集就是F(A)

 

 

定义两个集合A,B中的元素运算如下:

Fork(A, B)={a+b, a-b. b-a, a*b, a/b(b!=0), b/a(a!=0)}

 

采用4位二进制数表示集合A的真子集:

1111:{a0,a1,12,a3},  1110: {a1,a2,a3}……

首先初始化0001,0010,0100,1000,作为递归结束的条件。

1111 = {1110,0001}U……

采用数组S[i]保存集合F(i)

最终结果S[2n-1]即为集合A的所有元素通过四则运算得到的全部结果,检查S[2n-1]即可求出所得。

 

下面给出实现:

  1. #include <iostream>  
  2. #include <map>  
  3. #include <vector>  
  4. #include <set>  
  5. using namespace std;  
  6.   
  7. int N,m;  
  8. map<int,set<int>> S;  
  9.   
  10.   
  11. set<int>& f(int i)  
  12. {  
  13.     if (!S[i].empty())  
  14.         return S[i];  
  15.     for (int x=1; x<=i/2; x++)  
  16.     {  
  17.         if( (x&i) == x)  
  18.         {  
  19.             set<int>&s1 = f(i-x);  
  20.             set<int>&s2 = f(x);  
  21.             for(set<int>::iterator it1=s1.begin(); it1!=s1.end(); it1++)  
  22.                 for(set<int>::iterator it2=s2.begin(); it2!=s2.end(); it2++)  
  23.                 {  
  24.                     S[i].insert(*it1 + *it2);  
  25.                     S[i].insert(*it1 - *it2);  
  26.                     S[i].insert(*it1 - *it2);  
  27.                     S[i].insert(*it1 * *it2);  
  28.                     if(*it2!=0) S[i].insert(*it1 / *it2);  
  29.                     if(*it1!=0) S[i].insert(*it2 / *it1);  
  30.                 }  
  31.         }  
  32.     }  
  33.     return S[i];  
  34. }  
  35.   
  36. int main()  
  37. {  
  38.     int a[4];  
  39.     N=4;  
  40.     int i;  
  41.     for(i=0; i<N;i++)   
  42.     {  
  43.         cin>>a[i];  
  44.         S[1<<i].insert(a[i]);  
  45.     }  
  46.     int num = (1<<N) - 1;  
  47.     f(num);  
  48.     int c = S[num].count(24);  
  49.     cout<<"count: "<<c<<endl;  
  50. }  

这是小菜我自己写的,只能输出能与不能。参考网上别的大牛写的,觉得好生惭愧,推荐下面这个,上面这个各位看官就当反面典型看吧~

 

[html]  view plain copy print ?
  1. #include <iostream>  
  2. #include <set>  
  3. #include <string>  
  4. #include <cmath>  
  5. using namespace std;  
  6.   
  7. #define N   4   // 4张牌,可变  
  8. #define RES 24  // 运算结果为24,可变  
  9. #define EPS 1e-6  
  10.   
  11. struct Elem  
  12. {  
  13.     Elem(double r, string& i):res(r),info(i){}  
  14.     Elem(double r, char* i):res(r),info(i){}  
  15.     double res; // 运算出的数据  
  16.     string info; // 运算的过程  
  17.     bool operator<(const Elem& e) const  
  18.     {  
  19.         return res < e.res; // 在set的红黑树插入操作中需要用到比较操作  
  20.     }  
  21. };  
  22.   
  23. int A[N];   // 记录N个数据  
  24. //用二进制数来表示集合和子集的概念,0110表示集合包含第2,3个数  
  25. set<Elem> vset[1<<N];   // 包含4个元素的集合共有16个子集0-15  
  26.   
  27. set<Elem>& Fork(int m)  
  28. {  
  29.     // memo递归  
  30.     if (vset[m].size())  
  31.     {  
  32.         return vset[m];  
  33.     }  
  34.     for (int i=1; i<=m/2; i++)//因为分别计算Fork(i)与Fork(m-i),所以计算一半就行了  
  35.         if ((i&m) == i)  
  36.         {  
  37.             set<Elem>s1 = Fork(i);  
  38.             set<Elem>s2 = Fork(m-i);  
  39.             // 得到两个子集合的笛卡尔积,并对结果集合的元素对进行6种运算  
  40.             for (set<Elem>::iterator cit1=s1.begin(); cit1!=s1.end(); cit1++)  
  41.                 for (set<Elem>::iterator cit2=s2.begin(); cit2!=s2.end(); cit2++)  
  42.                 {  
  43.                     string str;  
  44.                     str = "("+cit1->info+"+"+cit2->info+")";  
  45.                     vset[m].insert(Elem(cit1->res+cit2->res,str));  
  46.                     str = "("+cit1->info+"-"+cit2->info+")";  
  47.                     vset[m].insert(Elem(cit1->res-cit2->res,str));  
  48.                     str = "("+cit2->info+"-"+cit1->info+")";;  
  49.                     vset[m].insert(Elem(cit2->res-cit1->res,str));  
  50.                     str = "("+cit1->info+"*"+cit2->info+")";  
  51.                     vset[m].insert(Elem(cit1->res*cit2->res,str));  
  52.                     if (abs(cit2->res)>EPS)   
  53.                     {  
  54.                         str = "("+cit1->info+"/"+cit2->info+")";  
  55.                         vset[m].insert(Elem(cit1->res/cit2->res,str));  
  56.                     }  
  57.                     if (abs(cit1->res)>EPS)  
  58.                     {  
  59.                         str = "("+cit2->info+"/"+cit1->info+")";  
  60.                         vset[m].insert(Elem(cit2->res/cit1->res,str));  
  61.                     }  
  62.                 }  
  63.         }  
  64.         return vset[m];     //这一步一定不要忘了  
  65. }  
  66.   
  67. int main()  
  68. {  
  69.     int i;  
  70.     for (i=0; i<N; i++)  
  71.         cin >> A[i];  
  72.   
  73.     // 递归的结束条件;  
  74.     for (i=0; i<N; i++)  
  75.     {  
  76.         char str[10];  
  77.         sprintf(str,"%d",A[i]);  
  78.         vset[1<<i].insert(Elem(A[i],str));  
  79.     }  
  80.     Fork((1<<N)-1);//开始1111 表示四个数;   
  81.       
  82.     // 显示算出24点的运算过程;  
  83.     for (set<Elem>::iterator it=vset[(1<<N)-1].begin(); it!=vset[(1<<N)-1].end(); it++)  
  84.     {  
  85.         if (abs(it->res-RES) < EPS)  
  86.             cout << it->info << endl;  
  87.     }  
  88. }  


 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值