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

来源:互联网 发布:海湾消防主机联动编程 编辑:程序博客网 时间:2024/05/22 14:23

问题描述:

    给玩家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个数

伪代码:

F(Array){  if(Array.Length<2)  {    if(最终结果为24) 输出表达式;    else 无法构造   }  for each(从数组中任意取两个数的组合)  {      for each(运算符(+, -, *,  /))      {         1.计算该组合在此运算符下的结果;         2.将组合中的两个数从原数组中移除,并将步骤1的结果放入数组;         3.对新数组递归调用f,找到一个表达式则返回;         4.将步骤1的结果移除,并将该组合中的两个数重新放回该数组;       }  }}


 

 

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]即可求出所得。

 

下面给出实现:

#include <iostream>#include <map>#include <vector>#include <set>using namespace std;int N,m;map<int,set<int>> S;set<int>& f(int i){if (!S[i].empty())return S[i];for (int x=1; x<=i/2; x++){if( (x&i) == x){set<int>&s1 = f(i-x);set<int>&s2 = f(x);for(set<int>::iterator it1=s1.begin(); it1!=s1.end(); it1++)for(set<int>::iterator it2=s2.begin(); it2!=s2.end(); it2++){S[i].insert(*it1 + *it2);S[i].insert(*it1 - *it2);S[i].insert(*it1 - *it2);S[i].insert(*it1 * *it2);if(*it2!=0) S[i].insert(*it1 / *it2);if(*it1!=0) S[i].insert(*it2 / *it1);}}}return S[i];}int main(){int a[4];N=4;int i;for(i=0; i<N;i++) {cin>>a[i];S[1<<i].insert(a[i]);}int num = (1<<N) - 1;f(num);int c = S[num].count(24);cout<<"count: "<<c<<endl;}

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

 

#include <iostream>#include <set>#include <string>#include <cmath>using namespace std;#define N4// 4张牌,可变#define RES24// 运算结果为24,可变#define EPS 1e-6struct Elem{Elem(double r, string& i):res(r),info(i){}Elem(double r, char* i):res(r),info(i){}double res;// 运算出的数据string info; // 运算的过程bool operator<(const Elem& e) const{return res < e.res; // 在set的红黑树插入操作中需要用到比较操作}};int A[N];// 记录N个数据//用二进制数来表示集合和子集的概念,0110表示集合包含第2,3个数set<Elem> vset[1<<N];// 包含4个元素的集合共有16个子集0-15set<Elem>& Fork(int m){// memo递归if (vset[m].size()){return vset[m];}for (int i=1; i<=m/2; i++)//因为分别计算Fork(i)与Fork(m-i),所以计算一半就行了if ((i&m) == i){set<Elem>& s1 = Fork(i);set<Elem>& s2 = Fork(m-i);// 得到两个子集合的笛卡尔积,并对结果集合的元素对进行6种运算for (set<Elem>::iterator cit1=s1.begin(); cit1!=s1.end(); cit1++)for (set<Elem>::iterator cit2=s2.begin(); cit2!=s2.end(); cit2++){string str;str = "("+cit1->info+"+"+cit2->info+")";vset[m].insert(Elem(cit1->res+cit2->res,str));str = "("+cit1->info+"-"+cit2->info+")";vset[m].insert(Elem(cit1->res-cit2->res,str));str = "("+cit2->info+"-"+cit1->info+")";;vset[m].insert(Elem(cit2->res-cit1->res,str));str = "("+cit1->info+"*"+cit2->info+")";vset[m].insert(Elem(cit1->res*cit2->res,str));if (abs(cit2->res)>EPS) {str = "("+cit1->info+"/"+cit2->info+")";vset[m].insert(Elem(cit1->res/cit2->res,str));}if (abs(cit1->res)>EPS){str = "("+cit2->info+"/"+cit1->info+")";vset[m].insert(Elem(cit2->res/cit1->res,str));}}}return vset[m];     //这一步一定不要忘了}int main(){int i;for (i=0; i<N; i++)cin >> A[i];// 递归的结束条件;for (i=0; i<N; i++){char str[10];sprintf(str,"%d",A[i]);vset[1<<i].insert(Elem(A[i],str));}Fork((1<<N)-1);//开始1111 表示四个数; // 显示算出24点的运算过程;for (set<Elem>::iterator it=vset[(1<<N)-1].begin(); it!=vset[(1<<N)-1].end(); it++){if (abs(it->res-RES) < EPS)cout << it->info << endl;}}


 

 

 

 

 

 

 

原创粉丝点击