最近简单地研究了一些关于二十四点游戏的知识,然后写了一个挺zz的程序去解24点,后来又用他做了一个24点“查询辞典”之类的东西,感觉还不错。以后有时间的话再进行深入研究,先把程序挂在这儿。
/// Day37 2019.5.1 二十四点字典
#include <cstdio>
#include <set> /// fot set
#include <cmath> /// for pow, fabs
#include <cstring> /// for memset, strcpy
#include <cctype>
#include <algorithm>
#include <ctime> /// for clock
using namespace std;
double replace(double x, int t) { /// 替代值 (t!=0)
return pow(x, 1.0/t);
/// t = 1 表示不改变原有的值
/// else 表示用一个无理数替代原有的值
}
struct stringFile { /// 用于从字符串中截取
char str[128]; int top;
void load(const char* ch) { /// 装载字符串
memset(str, 0x00, sizeof(str));
top = 0;
for(int i = 0; ; i ++) {
str[i] = ch[i];
if(ch[i] == 0) return;
}
}
char front() { /// 获得第一个字符
return str[top];
}
void pop() { /// 弹出第一个字符
if(str[top] != 0) top ++;
}
bool end() { /// 判断是否已经到文件结尾
return str[top] == 0;
}
void jump() { /// 跳过首端空格
while(!end() && !isgraph(front())) {
pop();
}
}
void readstr(char* sto) { /// 读取一个相连串
jump(); /// 跳过首端空格
if(end()) {
sto[0] = 0; /// 什么都不剩了
}else {
sto[0] = front(); pop();
if(sto[0]=='(' || sto[0]==')') {
sto[1] = 0; /// 读到括号 读取完成
return;
}else if(sto[0]=='+' || sto[0]=='-' ||
sto[0]=='*' || sto[0]=='/') {
sto[1] = 0; /// 读到运算符 读取完成
return;
}else if('0'<=sto[0] && sto[0]<='9') { /// 发现数值
for(int i = 1; ; i ++) {
if('0'<=front() && front()<='9') {
sto[i] = front(); pop(); /// 都区连续数值
}else {
sto[i] = 0; break;
}
}
return;
}else {
strcpy(sto, "Error");
/// 发现未知字符
}
}
}
};
const double erroNum = -123456789.0;
char opes[5] = {0, '+', '-', '*', '/'};
double cal(double x, char ope, double y) {
switch(ope) {
case '+':
if(fabs(x-erroNum)<1e-6 || fabs(y-erroNum)<1e-6) return erroNum; /// 计算错误的传递
return x+y;
case '-':
if(fabs(x-erroNum)<1e-6 || fabs(y-erroNum)<1e-6) return erroNum;
return x-y;
case '*':
if(fabs(x-erroNum)<1e-6 || fabs(y-erroNum)<1e-6) return erroNum;
return x*y;
case '/':
if(fabs(y) < 1e-6) { /// 除以零
/// 忽略这个计算错误即可
return erroNum; /// error
}else {
return x/y;
}
default:
/// 这个错误 不能忽略
fprintf(stderr, "调用 cal 函数式 算符 ope{%c} 非法", ope);
while(1);
return erroNum; /// error
}
}
double get(stringFile& sf, int t) { /// 得到计算结果
char tmp[64];
sf.readstr(tmp); /// 读取一个字符串
if('0'<=tmp[0] && tmp[0]<='9') { /// 遇到数值
return replace(atof(tmp), t);
/// 对于真实的数值采用替代符
/// 对于中间结果不采用替代符
}else {
if(tmp[0] == '(') { /// 表达式的开始
double x, y, ans;
char ope[16];
x = get(sf, t); /// 得到第一个参数
sf.readstr(ope); /// 得到算符
y = get(sf, t); /// 得到第二个参数
ans = cal(x, ope[0], y); /// 得到计算结果
sf.readstr(tmp); /// 去掉剩下的一个右括号
return ans;
if(tmp[0] != ')') {
fprintf(stderr, "表达式缺少右括号!");
while(1);
}
}else {
///发现错误
fprintf(stderr, "表达式中出现不合法的开头标签{%s}", tmp);
while(1);
}
}
}
/// 定义表达式的格式:
/// 1.一个数
/// 2.左括号 + 表达式 + 运算符 + 表达式 + 右括号
/// 注意: 对于所有运算都强制使用括号
struct Exp { /// 描述一个表达式
char str[64]; /// 以字符串的形式储存表达式
void load(const char* ch) {
strcpy(str, ch);
}
double getval(int t = 1) { /// 得到计算结果
stringFile sf;
sf.load(str); /// 加载字符串
return get(sf, t);
}
};
struct feature { /// 描述一个表达式的特征
long long a[6]; /// a[1] to a[5]
void load(Exp& exp) { /// 得到一个表达式的特征
for(int i = 1; i <= 5; i ++) {
a[i] = exp.getval(i) * 1000; /// 考虑小数点后三位
}
}
};
bool operator < (const feature& f1, const feature& f2) {
for(int i = 1; i <= 5; i ++) {
if(f1.a[i] != f2.a[i]) /// 发现不同
return f1.a[i] < f2.a[i];
}
return false; /// 相同
}
bool operator == (const feature& f1, const feature& f2) {
/// 判断两个特征数列是否相同
for(int i = 1; i <= 5; i ++) {
if(f1.a[i] != f2.a[i]) /// 发现不同
return false;
}
return true; /// 相同
}
set<feature> dic;
int ary[25][5] = { /// 枚举全排列
{0, 0, 0, 0, 0},
{0, 1, 2, 3, 4},
{0, 1, 2, 4, 3},
{0, 1, 3, 2, 4},
{0, 1, 3, 4, 2},
{0, 1, 4, 2, 3},
{0, 1, 4, 2, 3},
{0, 2, 1, 3, 4},
{0, 2, 1, 4, 3},
{0, 2, 3, 1, 4},
{0, 2, 3, 4, 1},
{0, 2, 4, 1, 3},
{0, 2, 4, 3, 1},
{0, 3, 1, 2, 4},
{0, 3, 1, 4, 2},
{0, 3, 2, 1, 4},
{0, 3, 2, 4, 1},
{0, 3, 4, 1, 2},
{0, 3, 4, 2, 1},
{0, 4, 1, 2, 3},
{0, 4, 1, 3, 2},
{0, 4, 2, 1, 3},
{0, 4, 2, 3, 1},
{0, 4, 3, 1, 2},
{0, 4, 3, 2, 1}
};
int cnt;
void solve(double A, double B, double C, double D) {
dic.clear();
cnt = 0;
double carrier[5] = {0, A, B, C, D};
for(int d = 1; d <= 24; d ++) { /// 枚举全排列
A = carrier[ary[d][1]];
B = carrier[ary[d][2]];
C = carrier[ary[d][3]];
D = carrier[ary[d][4]]; /// 枚举全排列
for(int i = 1; i <= 4; i ++) {
for(int j = 1; j <= 4; j ++) {
for(int k = 1; k <= 4; k ++) {
/// 枚举三个运算符
char tmp[64]; Exp exp; feature ftr;
/// Mode 1
sprintf(tmp, "((%d %c %d) %c (%d %c %d))",
int(A+0.1), opes[i], int(B+0.1), opes[j], int(C+0.1), opes[k], int(D+0.1));
exp.load(tmp);
ftr.load(exp); /// 加载表达式
if(ftr.a[1]==24000 && dic.find(ftr) == dic.end()) {
dic.insert(ftr);
//printf(" %s\n", tmp);
cnt ++;
}
/// Mode 2
sprintf(tmp, "(((%d %c %d) %c %d) %c %d)",
int(A+0.1), opes[i], int(B+0.1), opes[j], int(C+0.1), opes[k], int(D+0.1));
exp.load(tmp);
ftr.load(exp); /// 加载表达式
if(ftr.a[1]==24000 && dic.find(ftr) == dic.end()) {
dic.insert(ftr);
//printf(" %s\n", tmp);
cnt ++;
}
/// Mode 3
sprintf(tmp, "((%d %c (%d %c %d)) %c %d)",
int(A+0.1), opes[i], int(B+0.1), opes[j], int(C+0.1), opes[k], int(D+0.1));
exp.load(tmp);
ftr.load(exp); /// 加载表达式
if(ftr.a[1]==24000 && dic.find(ftr) == dic.end()) {
dic.insert(ftr);
//printf(" %s\n", tmp);
cnt ++;
}
/// Mode 4
sprintf(tmp, "(%d %c ((%d %c %d) %c %d))",
int(A+0.1), opes[i], int(B+0.1), opes[j], int(C+0.1), opes[k], int(D+0.1));
exp.load(tmp);
ftr.load(exp); /// 加载表达式
if(ftr.a[1]==24000 && dic.find(ftr) == dic.end()) {
dic.insert(ftr);
//printf(" %s\n", tmp);
cnt ++;
}
/// Mode 5
sprintf(tmp, "(%d %c (%d %c (%d %c %d)))",
int(A+0.1), opes[i], int(B+0.1), opes[j], int(C+0.1), opes[k], int(D+0.1));
exp.load(tmp);
ftr.load(exp); /// 加载表达式
if(ftr.a[1]==24000 && dic.find(ftr) == dic.end()) {
dic.insert(ftr);
//printf(" %s\n", tmp);
cnt ++;
}
}
}
}
}
//if(cnt == 0) {
// printf(" No solution.\n");
//}
}
int main() {
freopen("a.txt", "w", stdout);
int p = 0, q = 0;
for(int A = 1; A <= 13; A ++) {
for(int B = A; B <= 13; B ++) {
for(int C = B; C <= 13; C ++) {
for(int D = C; D <= 13; D ++) {
if(A<=10 && B<=10 && C<=10 && D<=10) continue; /// 至少 JQK 中的一个
fprintf(stderr, "A = %2d, B = %2d, C = %2d, D = %2d, clock = %d\n",
A, B, C, D, clock());
solve(A, B, C, D);
if(cnt == 0) {
if(p != A) { /// 新一段的开始
printf("----------\n");
printf("--> %d %d %d %d\n", A, B, C, D);
p = A; q = B;
}else if(q != B) { /// 新一子段的开始
printf("--> %d %d %d %d\n", A, B, C, D);
q = B;
}else {
printf("-- %d %d %d %d\n", A, B, C, D);
}
}
}
}
}
}
freopen("CON", "w", stdout);
return 0;
}
为了让“辞典”更加简洁,上面采用了一种哈希去重的策略,虽说有一些瑕疵,但是效果很棒(比如会把X+(Y-Y)
和X*(Y/Y)
视为等价表达式)。