题目
digit puzzle 数字谜 Uva12107
给一个不完整表达式,要求修改尽量少的数,使修改后的数字谜只有唯一解。空格和数字可以随意替换,但不能增删,表达式中所有涉及的数必须是没有前导零的正数。输入数字谜一定形如a*b=c,其中a、b、c分别最多有2、2、4位。用下划线表示未知数字。输入保证会有解,即有经过变换后肯定能有一个表达式有唯一解。如果有多个解,输出字典序最小的解, 下划线比数字小
输入
_*__=78 //表达式 答案不唯一 1*78=78 2*39=78
输出
_*_7=_8 //表达式唯一解 4*17=68; 右原始表达式第3个下划线和7交换1次得到
tip:
7*__=_8 //表达式也有唯一解 7*14=98; 但修改次数也是1 但字典序不是最小
思路
初始状态S
while True:
每次交换一个不同的数字或下划线 形成新的表达式S
if 表达式S 有唯一解:
停止搜索,结束
1、首先要有一个逻辑 判断表达式解是否唯一
2、每次交换一个不同的数字或下划线 形成新的表达式,校验是否唯一解,不唯一重复步骤2
问题
1、如何保证修改次数最小?
IDA*深度迭代搜索
2、如何保证修改次数相同时 字典序最小?
在搜索时保证首先搜索字典序最小的状态,那么第一个搜索到的解就是答案
3、如何判断表达式解是否唯一?
初始状态S
all=0
def isOk(S):
if S 中没有空格:
if 校验表达式==成立:
all++;
else for 下划线 in 所有的下换线:
替换下划线 = 0,1,2,...,9 获得新的状态S1。
递归S1
#上述思路 可以优化,不过下面代码没有实现
# 1\ 替换a、b中的下换线。计算c=a*b, 匹配c与表达式中c
# 2\ 一旦all >=2后 就不需要查找了。肯定不是唯一解
4、如何存储表达式 (这是任何题目都避免不了的问题)
因为最多涉及8个数字或下划线 ,用4个bit存储一个数字, 正好用1个int型=32位;
因为下划线比数字小,所以空格用0=0000表示 数字用原始数字+1存储即[0001, 1010] 不存在用NAN=15=1111表示。如果数字是无符号的int型,那么数字越小对应的字典序也越小
案例验证
输入 _*__=78
输出 _*_7=_8
代码
/*
* 思路
* 最多涉及8个数字 用4个bit存储一个数字, 正好32位=1个int型; 空格用0=0000表示 数字用原始数字+1存储即[0001, 1010] 不存在用NAN=15=1111表示,一定是数无符号整型
* 初始状态s0
* 转移方程 s = { sj | si中8个数字或空格, 除15外 任意两个不同的值交换得到sj }
*
*/
#define MAXDEPTH 10
#define NAN 15
// 交换s[i]<-->s[j]
#define chg(s, i, j)\
do{\
int __i, __j, __k;\
for( __i=(i)*4, __j=(j)*4, __k=0; __k<4; __k++, __i++, __j++)\
{\
if ( (s&1<<__i)>>__i != (s&1<<__j)>>__j )\
{\
if ( s&1<<__i ) { s &= ~(1<<__i); s |= (1<<__j); }\
else { s &= ~(1<<__j); s |= (1<<__i); }\
}\
}\
}while(0)
//获取s[i]的数字
#define slice(s, i) (s>>(i)*4&15)
//更新s[i]的数字=v
#define update(s, i, v) do{ s &= ~(15<<(i)*4); s |= (v)<<(i)*4; } while(0)
int MaxDepth;
int depth;
int ans;
static int okNum(int s, int index)
{
int i, k, v, a, b, c, num;
num = 0;
// 所有空格替换完毕
if (index==-1)
{
// 解析a,b,c
a=b=c=0;
for(i=0; i<8; i++)
{
// 跳过NAN
if( slice(s,i) == NAN ) continue;
if(i==0) {
c += (slice(s,i)-1);
}
else if(i==1) {
c += (slice(s,i)-1) * 10;
}
else if(i==2) {
c += (slice(s,i)-1) * 100;
}
else if(i==3) {
c += (slice(s,i)-1) * 1000;
}
else if(i==4) {
b += (slice(s,i)-1);
}
else if(i==5) {
b += (slice(s,i)-1) * 10;
}
else if(i==6) {
a += (slice(s,i)-1);
}
else if(i==7) {
a += (slice(s,i)-1) * 10;
}
}
return a*b==c ? 1:0 ;
}
/*
* 遇到空格0 时填充数字1,2,...,10, 保证没有前导0
* 递归到index-1
*/
if( slice(s,index) == 0 )
{
for(v=0; v<10; v++)
{
// 1、index=3 5 7时 不能为0.
// 2、前一个index+1=1(值为0)或NAN(不存在)时 不能为0
if(v==0)
{
if( index==3 || index==5 || index==7) continue;
if( slice(s,index+1)==1 || slice(s,index+1)==NAN) continue;
}
update(s, index, v+1);
num += okNum(s, index-1);
}
}
else
{
num += okNum(s, index-1);
}
return num;
}
/* 搜索第一个符合条件的答案 */
static int search(int s)
{
int i,j, g, v, a, b, k;
unsigned int tmp, sNew[28]; // 数量<=8时 任意交换两个最多7*8/2=28种可能
if (depth >= MaxDepth ) return 0;
if( okNum(s, 7)==1 )
{
ans = s;
return 1;
}
// 初始化sNew 在有序集合{s|交换s中任意两个不相同的数字或空格, si<sj }
g=-1;
for(i=0; i<8; i++) if( slice(s, i)!=NAN )
{
for(j=i+1; j<8; j++) if( slice(s, j)!=NAN )
{
if(slice(s, i)==slice(s, j)) continue;
tmp = s;
chg(tmp, i, j);
k=g;
while(k>=0 && sNew[k] > tmp) {
sNew[k+1]=sNew[k];
k--;
}
sNew[k+1]=tmp;
g++;
}
}
// 寻找需要交换的a, b 使得交换后 中最小k小
// 按照交换后字典序 曾序递归
for(i=0; i<=g; i++)
{
depth++;
if ( search(sNew[i]) ) return 1;
depth--;
}
return 0;
}
int main()
{
int i,j,k,l, n, s0;
char text[8+1];
scanf("%s", text);
//初始化答案
ans=-1;
// 初始化s0
l=-1;
while(text[++l]!='\0');
s0=-1;
k=0;
for(i=0,j=l-1; j>=0; j--)
{
if( text[j] == '=' ) i = 4;
else if( text[j] == '*') i = 6;
else
{
update(s0, i, (text[j]=='_'?0:(text[j]-'0')+1));
i++;
}
}
// printf("s0:");
// ppp(&s0, 32);
// IDA*搜索
for(MaxDepth=1; MaxDepth<MAXDEPTH; MaxDepth++)
if( search(s0) ) break;
// 打印答案
// printf("ans:");
// ppp(&s0, 32);
if( ans==-1 ) printf("no solution\n");
else
{
for(i=7; i>=0; i--)
{
if(i==5) printf("*");
if(i==3) printf("=");
j = slice(ans, i);
if(j==0) printf("_");
else if(j!=NAN) printf("%d", j-1);
}
printf("\n");
}
return 0;
}