UVa 12107 Digit Puzzle 数字谜题 迭代加深搜剪枝 IDA*

文章介绍了一种名为DigitPuzzle的题目,要求确定使得公式A*B=C唯一解的最少修改次数。解题方法采用迭代加深深度优先搜索(IDDFS),在搜索过程中注意剪枝策略,如当需要修改的次数大于剩余位置时可直接剪枝。在找到可能的解答后,还需验证是否存在唯一解,这里通过填充前两个数字并推断第三个数字来判断。代码实现中展示了如何处理这个任务,包括避免前导零和保持字典序最小的策略。
摘要由CSDN通过智能技术生成

题目链接:Digit Puzzle
题目描述:

给定一个 A ∗ B = C A*B=C AB=C的公式,其中有些位置是星号表示可以是任意数字问你需要修改多少位才能让该公式的解唯一确定,如果有多种答案优先输出修改次数少的,如果依然有多种输出字典序最小的解,需要注意的是修改可以把数字改为星号,星号改为数字,但是不能减少或增加每个数字的位数,同时数字不能含有前导零。
例如输入:
∗ ∗    ∗ ∗    ∗ ∗ ∗ **\ \ **\ \ ***     
输出应该为:
$ ∗ ∗    ∗ ∗    1 ∗ 1 **\ \ **\ \ 1*1     11
这是因为上述的输出只有 11 ∗ 11 = 121 11*11=121 1111=121一种解。具体的输出格式需要参看原题目。

题解:

本题我们直接进行搜索即可,只是需要注意一些剪枝的地方。
我们需要使用 I D ID ID(迭代加深搜)我们只需要依次搜索每个位置的可能值,在填入值的时候需要注意先后顺序应该先填入星号,然后数字从小到大这样能够保证字典序最小,一个剪枝:如果还需要修改的个数大于剩余的位置个数那么可以直接进行剪枝。
在搜索到答案之后,我们还需要判断当前答案是否有唯一的解,判断的过程我们同样需要依次填入数字,不过需要注意的是,在判断时候我们实际上只需要把前两个数字的空白都填完就可以进行判断了这样可以推断出第三个数字应该为多少,而如果将三个数字都填完在进行判断的话由于第三个数最多有四位,每个位置有十个数字可以填入,那么复杂度就可能是原来的 10000 10000 10000倍,这样就会超时。

代码:

#include <bits/stdc++.h>

const int MAXN = 10;

using namespace std;

char a[MAXN], b[MAXN], c[MAXN];
int lenA, lenB, lenC, caseID, cnt, maxDepth;

bool getInput()
{
    cin >> a;
    if (a[0] == '0') { return false; }
    cin >> b >> c;
    lenA = strlen(a);
    lenB = strlen(b);
    lenC = strlen(c);
    return true;
}

void getCnt(int pa, int pb);

void fillNumber(char *a, int startPos, int len, int npa, int npb)
{
    if (isdigit(a[startPos])) { getCnt(npa, npb); }
    else {
        for (int number = 0; number <= 9; number++) {
            if (startPos == 0 && len != 1 && number == 0) { continue; }
            a[startPos] = number + '0';
            getCnt(npa, npb);
        }
        a[startPos] = '*';
    }
}

void getCnt(int pa, int pb) {
    if (cnt > 1) { return; }
    if (a[lenA - 1] != '*' && b[lenB - 1] != '*' && c[lenC - 1] != '*'
        && (a[lenA - 1] - '0') * (b[lenB - 1] - '0') % 10 != c[lenC - 1] - '0') { return; }
    if (pb == lenB) {
        int numberA = atoi(a);
        int numberB = atoi(b);
        int numberC = numberA * numberB;
        for (int i = lenC - 1; i >= 0; i--) {
            if (i == 0 && numberC == 0 && lenC > 1) { return; }
            if (c[i] != '*' && numberC % 10 != c[i] - '0') { return; }
            numberC /= 10;
        }
        if (numberC != 0) { return; }
        cnt++;
        return;
    }
    if (pa != lenA) { fillNumber(a, pa, lenA, pa + 1, pb); }
    else if (pb != lenB) { fillNumber(b, pb, lenB, pa, pb + 1);}
}

bool check()
{
    cnt = 0;
    getCnt(0, 0);
    return cnt == 1;
}

bool dfs(int nowDepth, int pa, int pb, int pc);

bool change(char *a, int startPos, int len, int nowDepth, int npa, int npb, int npc)
{
    char temp = a[startPos];
    if (a[startPos] != '*') { // 尝试改为星号,星号的ANSCII码小,所以需要先搜索改成星号的情况
        a[startPos] = '*';
        if (dfs(nowDepth + 1, npa, npb, npc)) { return true; }
        a[startPos] = temp;
    } else {
        if (dfs(nowDepth, npa, npb, npc)) { return true; }
    }
    for (int number = 0; number <= 9; number++) { // 尝试改为数字
        if (len > 1 && startPos == 0 && number == 0) { continue; } // 不能含有前导零
        a[startPos] = number + '0';
        if (temp - '0' == number) {
            if (dfs(nowDepth, npa, npb, npc)) { return true; }
            continue;
        }
        if (dfs(nowDepth + 1, npa, npb, npc)) { return true; }
    }
    a[startPos] = temp;
    return false;
}

bool dfs(int nowDepth, int pa, int pb, int pc)
{
    if (nowDepth == maxDepth) { return check(); }
    // 剩余能够修改的位置少于还需要进行的修改次数则直接进行剪枝
    if (maxDepth - nowDepth > lenA - pa + lenB - pb + lenC - pc) { return false; }
    if (pa != lenA) {
        if (change(a, pa, lenA, nowDepth, pa + 1, pb, pc)) { return true; }
    } else if (pb != lenB) {
        if (change(b, pb, lenB, nowDepth, pa, pb + 1, pc)) { return true; }
    } else {
        if (change(c, pc, lenC, nowDepth, pa, pb, pc + 1)) { return true; }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false);
    while (getInput()) {
        for (maxDepth = 0; ; maxDepth++) {
            if (dfs(0, 0, 0, 0)) {
                caseID++;
                cout << "Case " << caseID << ": ";
                cout << a << " " << b << " " << c << endl;
                break;
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值