现在再看《编程之美》,昨天看到一个24点的问题:给玩家4张牌,每张面值在1~13之间,允许其中有数值相同的牌,采用加减乘除的方法,允许中间有小数,并且可以使用括号,但每张牌只能使用一次,试着构造一个多项式,使其运算结果为24;
这里首先想的就是利用穷举,就是先4个数的全排列,然后有三个符号,然后每个符号有四种可能,然后是有括号,这种方法比较复杂,所以就直接跳过了这个,书里给出了完整的代码,然后说一下比较优化的方法,采用分治方法,即把四个数当作一个集合A, 然后分成两个集合B1,B2,然后分别对B1和B2中的元素分别进行计算即F(B1),F(B2),然后在对B1,B2中的元素在进行分出子集,然后计算,最后,再将B1,B2的两个子集进行混合,进行计算。可以看出,这个就要利用递归解决了,为了把效率提高,减少重复的计算,也要采用动态规划的方法了,这里有个问题就是对于加法和乘法满足交换律,但是减法和除法是不满足的,所以一共需要进行六种运算;《编程之美》中对于子集的计算方法是利用二进制的方法,即4个数的总的集合是1111,然后如果有第一个数的集合就利用0001表示;
采用优化过的方法:
#include <cstdio>
#include <iostream>
#include <set>
#include <string>
#include <cmath>
using namespace std;
#define N 4
#define RES 24
#define EPS 1e-6
struct Elem
{
double a;
string s;
Elem(double _a, string _s) : a(_a), s(_s){}
Elem(double _a, char *_s) : a(_a), s(_s){}
bool operator<(const Elem &m) const //这里注意一看是没注意后面加const
{
if( a < m.a )
{
return true;
}
return false;
}
};
set<Elem> vset[1<<N];
set<Elem> & Fork(int s)
{
if( vset[s].size() )//说明这组已经计算过了
{
return vset[s];
}
else
{
for(int i=1; i<=s/2; i++)//寻找子集 注意这里要是s的一半 要不就重复计算了
{
if( (i&s) == i )//只有在等于的时候说明是子集
{
set<Elem> &s1 = Fork(i);
set<Elem> &s2 = Fork(s-i);
for(set<Elem>::iterator m=s1.begin(); m!=s1.end(); m++)
{
for(set<Elem>::iterator n=s2.begin(); n!=s2.end(); n++)
{
string ss;
double dd;//开始六种运算方式
ss = "(" + m->s + "+" + n->s + ")";//实现加法
dd = m->a + n->a;
vset[s].insert( Elem(dd, ss) );
ss = "(" + m->s + "-" + n->s + ")"; //实现减法
dd = m->a - n->a;
vset[s].insert( Elem(dd, ss) );
ss = "(" + n->s + "-" + m->s + ")"; //实现减法
dd = n->a - m->a;
vset[s].insert( Elem(dd, ss) );
ss = "(" + n->s + "*" + m->s + ")"; //实现乘法
dd = n->a * m->a;
vset[s].insert( Elem(dd, ss) );
ss = "(" + n->s + "/" + m->s + ")"; //实现除法
dd = n->a / m->a;
vset[s].insert( Elem(dd, ss) );
ss = '(' + m->s + '/' + n->s + ')'; //实现除法
dd = m->a / n->a;
vset[s].insert( Elem(dd, ss) );
}
}
}
}
return vset[s];
}
}
int main()
{
int d;
char s[22];
for(int i=0; i<4; i++)
{
cin>>d;
sprintf(s, "%d", d);
vset[1<<i].insert( Elem(d, s) );
}
Fork( (1<<N) - 1 );
for(set<Elem>::iterator m=vset[(1<<N)-1].begin(); m!=vset[(1<<N)-1].end(); m++)
{
if( abs(m->a - RES) < EPS )
{
cout<<m->s<<endl;
}
}
getchar();
return 0;
}