digit puzzle 数字谜 Uva12107

题目

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值