ZOJ 1145 Dreisam Equations(回溯)
During excavations in the Dreisamwuste, a desert on some far away and probably uncivilized planet, sheets of paper containing mysterious symbols had been found. After a long investigation, the project scientists have concluded that the symbols might be parts of equations. If this were true, it would be proof that the Dreisamwuste was civilized a long long time ago.
The problem, however, is that the only symbols found on the sheets are digits, parantheses and equality signs. There is strong evidence that the people living in the Dreisamwuste knew only of three arithmetic operations: addition, subtraction, and multiplication. It is also known that the people of the Dreisamwuste did not have prioritization rules for arithmetic operations - they evaluate all terms strictly left to right. For example, for them the term 3 + 3 * 5 would be equal to 30, and not 18.
But currently, the sheets do not contain any arithmetic operators. So if the hypothesis is true, and the numbers on the sheets form equations, then the operators must have faded away over time.
You are the computer expert who is supposed to find out whether the hypothesis is sensible or not. For some given equations (without arithmetic operators) you must find out if there is a possibility to place +, -, and * in the expression, so that it yields a valid equation. For example, on one sheet, the string ``18=7 (5 3) 2" has been discovered. Here, one possible solution is ``18=7+(5-3)*2". But if there was a sheet containing ``5=3 3", then this would mean that the Dreisamwuste people did not mean an equation when writing this.
Input
Each equation to deal with occupies one line in the input. Each line begins with a positive integer (less than 230) followed by an equality sign =. (For your convenience, the Dreisamwuste inhabitants used equations with trivial left sides only.) This is followed by up to 12 positive integers forming the right side of the equation. (The product of these numbers will be less than 230.) There might be some parentheses around groups of one or more numbers. There will be no line containing more than 80 characters. There is no other limit for the amount of the parentheses in the equation. There will always be at least one space or parenthesis between two numbers, otherwise the occurrence of white space is unrestricted.
The line containing only the number 0 terminates the input, it should not be processed.
Output
For each equation, output the line ``Equation #n:", where n is the number of the equation. Then, output one line containing a solution to the problem, i. e. the equation with the missing +, -, and * signs inserted. Do not print any white space in the equation.
If there is no way to insert operators to make the equation valid, then output the line ``Impossible".
Output one blank line after each test case.
Sample Input
18 = 7 (5 3) 2
30 = 3 3 5
18 = 3 3 5
5 = 3 3
0
Sample Output
Equation #1:
18=7+(5-3)*2
Equation #2:
30=3+3*5
Equation #3:
Impossible
Equation #4:
Impossible
题目中每个等式的右边,是一些数字和括号,要求放置适当的运算符+,-和*,使运算结果等于左边。对于每个可以放置运算符的位置,都可以放置+,-和*,即解空间是一颗完全三叉树,所以时间复杂度是O(3^n)。
本题采用回溯算法。
(1) 等式数据的读取
使用数组表示等式: char a[100];
该数组的位置指针为: int apos;
因为不知道等式有多少项,所以每次读取一行:gets(a);
如果字符串a中没有“=”,则表示输入结束。 而结束行的数字“0”,担心有多余的空格符。
(2) 构造伪表达式
从数组a中分离出数字、括号,并确定加入运算符的位置,构成伪表达式,存放到数组: int b[100];
该数组的位置指针为: int bpos;
对样例数据: 18=7 (5 3)2,数组b的值如下:、
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
值 |
7 |
-6 |
-1 |
5 |
-6 |
3 |
-2 |
-6 |
2 |
表中粗斜体是数字,-1表示左括号,-2表示右括号,-6表示该位置应该有一个运算符。为了便于判断,使用数组表示运算符在数组b中的位置: int op[30];
该数组的位置指针为: int opos;
如上例所示,数组op的值如下:
下标 |
0 |
1 |
2 |
值 |
1 |
4 |
7 |
表示数组b中,位置为1、4和7的地方,是运算符。
(3) 构造伪表达式
采用回溯算法构造表达式,函数是: void backtrack(int dep);
其中形参dep表示第几个运算符。
如果dep=opos,表示所有的运算符构造完毕
此时,表达式的结果等于左边的数iLeft时,即找到了答案。
如果dep<opos,对dep个运算符,可以放置+,-和*,并进入相应的子树。
(4) 括号的处理
采用递归函数: int bracket();
这是一个间接递归函数。也就是说,如果一队括号中还有括号的话,则继续调用backet()函数。也可以使用堆栈的方法实现。
代码如下:
1 # include<stdio.h> 2 # include<string.h> 3 # include<ctype.h> 4 5 # define LEFT -1 //左括号 6 # define RIGHT -2 //右括号 7 # define MUL -3 //*号 8 # define ADD -4 //+号 9 # define SUB -5 //-号 10 # define OP -6 //有运算符 11 # define NONE -10 12 13 char a[100]; //原始的等式数据 14 int b[100]; //伪表达式 15 int best[100]; //答案 16 int op[30]; //运算符在数组b中位置 17 int bn; //数组b的项数 18 int iLeft; //等式左边的数 19 int apos,bpos,opos; 20 int possible; //是否有解 21 22 //位置指针跳过空格 23 void space() 24 { 25 while(a[apos] && (a[apos] == ' ')) 26 apos++; 27 } 28 29 int compute(); 30 //计算括号里面的值 31 int bracket() 32 { 33 int sum; 34 if(b[bpos] == LEFT) 35 { 36 bpos++; //跳过左括号 37 sum = compute(); //计算括号里面的值(注意间接递归) 38 bpos++; //跳过右括号 39 } 40 else 41 sum = b[bpos++]; //没有括号 42 return sum; 43 } 44 45 //计算表达式 46 int compute() 47 { 48 int sum = bracket(); //右边第一个数 49 //对每一个运算符进行计算 50 while(b[bpos] == MUL || b[bpos]==ADD || b[bpos]==SUB) 51 { 52 int operation = b[bpos++]; //取出运算符 53 int ret = bracket(); //取出下一个数 54 //根据运算符进行计算 55 switch(operation) 56 { 57 case MUL: 58 sum *=ret; 59 break; 60 case ADD: 61 sum += ret; 62 break; 63 case SUB: 64 sum -= ret; 65 break; 66 } 67 } 68 return sum; 69 } 70 71 //回溯算法,构造表达式 72 void backtrack(int dep) 73 { 74 if(possible) return; //得到答案 75 int i; 76 //所有运算符的构造完毕 77 if(dep==opos) 78 { 79 bpos = 0; 80 int iRight = compute(); //右边表达式的值 81 if(iRight == iLeft) //得到答案 82 { 83 possible = 1; 84 for(i=0; i<bn; i++) 85 best[i] = b[i]; 86 } 87 return; 88 } 89 //当前节点的3个孩子结点,分别使用+,-和*运算符进行构造 90 b[op[dep]] = MUL; 91 backtrack(dep+1); 92 b[op[dep]] = ADD; 93 backtrack(dep+1); 94 b[op[dep]] = SUB; 95 backtrack(dep+1); 96 } 97 98 //输出结果 99 void print(int *q) 100 { 101 printf("%d=",iLeft); 102 int i; 103 for(i=0; i<bn; i++) 104 switch(q[i]) 105 { 106 case ADD: 107 printf("+"); 108 break; 109 case MUL: 110 printf("*"); 111 break; 112 case SUB: 113 printf("-"); 114 break; 115 case LEFT: 116 printf("("); 117 break; 118 case RIGHT: 119 printf(")"); 120 break; 121 case OP: 122 printf("?"); 123 break; 124 default: 125 printf("%d",q[i]); 126 break; 127 } 128 } 129 int main() 130 { 131 int iCase = 0; 132 int i; 133 while(gets(a) && strchr(a,'=')) 134 { 135 possible = 0; 136 for(i=0; i<90; i++) 137 b[i] = NONE; //初值 138 apos = 0; 139 sscanf(a,"%d",&iLeft); //左边的数 140 //将位置指针移到数字的后面 141 while(a[apos] && isdigit(a[apos])) apos++; 142 space(); 143 apos++; //跳过"=" 144 bn=0; 145 opos = 0; 146 while(space(),a[apos]) 147 { 148 if(a[apos] == '(') //左括号 149 { 150 b[bn++] = LEFT; 151 apos++; 152 continue; 153 } 154 if(a[apos]==')') //右括号 155 { 156 b[bn++] = RIGHT; 157 apos++; 158 } 159 else //读取数字 160 { 161 sscanf(a+apos,"%d",&b[bn++]); 162 while(a[apos] && isdigit(a[apos])) apos++; 163 } 164 space(); 165 if(a[apos] && a[apos] != ')') //如果不是结尾和‘)’,则有一个运算符 166 { 167 op[opos++] = bn; 168 b[bn++] = OP; 169 } 170 } 171 //回溯算法,构造表达式 172 backtrack(0); 173 printf("Equation #%d:\n",++iCase); 174 //表达式中只有一项 175 if(bn==1 &&iLeft == b[0]) 176 printf("%d=%d\n",iLeft,iLeft); 177 //无解 178 else if(bn==0 || !possible) 179 printf("Impossible\n"); 180 else 181 { 182 print(best); 183 printf("\n"); 184 } 185 printf("\n"); 186 } 187 return 0; 188 }