问题描述:
给玩家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 N 4 // 4张牌,可变
- #define RES 24 // 运算结果为24,可变
- #define EPS 1e-6
- struct 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-15
- set<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;
- }
- }