紫书 第7章 暴力求解法 习题7-8 Digit Puzzle UVA - 12107

紫书 第7章 暴力求解法 习题7-8 Digit Puzzle UVA - 12107

https://vjudge.net/problem/UVA-12107

https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3259

题目大意

本题给定一个不完整的等式,比如 7 X _ _ = 8_ 其中每个空可以填数字 0 - 9,如果这个等式有唯一解,那么这个等式就是一个good puzzle。
题目给出随机等式,想让我们用最小的改动找出唯一解。如果最小改动有多解,就输出字典序最小解。

数字范围:
对于等式 a x b = c,那abc 的最大位数是2,2,4。

表示方法
我们用来表示wildcard,即上边的空格。当用的时候,这个空可以是0-9中的任意数。

题目分析

A,b,c总共有8位,每位上除去现在的值,总共需要枚举10次 (0 - 9 和 *)。如果我们靠枚举所有的状态的话,最多总共需要枚举 11^8次才能找到解。

然而找到的值还需要一些计算才能判断是不是唯一解,这个计算需要O(10^4) 的时间复杂度(看下边的判断唯一解的部分),所以总共需要O(10^11)的时间复杂度。这样的话,程序会太慢。

然而,如果我们用迭代加深搜索的话,对于任意有解的情况,改动很难超过4次就能得到解。

这个估计我是这么得到的:
考虑所有a和b全是数字的情况,c一定是一个固定的值。只要c全是号那肯定是一个唯一解。所以对于全是数字或者的等式,最多只用换4个数就能找到定解。
其他的情况也不一定需满足上边的条件才会有解,但至少可以证明换4个数才能找到解已经是很极限的情况了。 这样的话我们可以把时间复杂度控制在O(10^8),这个解法的速度是完全可以接受的。

所以这道题里藏着两层枚举,外边的用迭代加深,里边的直接暴力枚举。2维爆力求解题。

判断唯一解

这个是这题里边的一个子问题。给定一个等式,我们需要判断这个等式有没有唯一解。
我能想到的最快方法是枚举a和b所有可能的值,然后看会不会和c吻合,如果吻合可能解+1。
最后看可能的解是不是1,如果不等于1就说明这个等式没有唯一解。

时间复杂度

如果a,b全是*,那么我们最多需要枚举10^ 4次才能得到答案。

这题要求输出最小字典序解。如果循环写的对的情况下可以一次找到解然后终止循环。但这个我觉得是个难点。重点是碰到和原字符一样的情况不可以直接跳过,而是继续循环但是不增加深度 (增加深度代表题换了当前字符)。最后写完AC的代码 330 ms,但是限时3000ms,所以我猜就算不注意顺序找到解再根据字典序去判断要不要替换答案也是能AC的,不过就是时间上不会那么好看。

另一个坑是不能有前置0。题目中不能有前置0的条件是在input里那一栏给出的,一开始我只是以为输入的数据里不会有前置0,但实际上是所有的解也不带着前置0。(坑了我一个WA)

AC 代码

找唯一解的部分我懒,就没有写递归而是直接4重循环,结果这么做还是可以的。
所以我猜如果主程序部分写个8重循环,可能也是能过的?

#include <bits/stdc++.h>

using namespace std;

const string cmap = "*0123456789";
const int clen = cmap.length();
string puzzle [3]; 
int kcase = 1;

bool vaild(int a, int b) {
    int c = a * b;
    const string& s = puzzle[2];
    for (int i = s.length() - 1; i >= 0; i--) {
        // when c == 0, s has leading 0
        if (!c) return false;
        int d = c % 10; c /= 10;
        if (!isdigit(s[i])) continue;
        if (d != s[i] - '0') return false;
    }
    return !c; // When c != 0, there are more digit in c not proceeded yet.
}

bool hasUniqueSolution() {
    bool found = false;
    string a = puzzle[0], b = puzzle[1];
    int ma0, ma1, mb0, mb1;
    // Use 2 instead of 1 to skip the leading 0, same as mb0 below.
    ma0 = a[0] == '*' ? 10 : 2; 
    ma1 = a.size() == 2 && a[1] == '*' ? 10 : 1;
    mb0 = b[0] == '*' ? 10 : 2;
    mb1 = b.size() == 2 && b[1] == '*' ? 10 : 1;
    
    for (int a0 = 1; a0 < ma0; a0++) { if (ma0 == 10) a[0] = a0 + '0';
        for (int a1 = 0; a1 < ma1; a1++) { if (ma1 == 10) a[1] = a1 + '0';
            for (int b0 = 1; b0 < mb0; b0++) { if (mb0 == 10) b[0] = b0 + '0';
                for (int b1 = 0; b1 < mb1; b1++) { if (mb1 == 10) b[1] = b1 + '0';
                    if (vaild(stoi(a),  stoi(b))) {
                        if (found) return false; // Not unique solution.
                        found = true;
                    }
                }
            }   
        }
    }
    return found; 
}

bool bt(int i, int j, int d, int maxd) {
    if (d == maxd) {
        if (hasUniqueSolution()) {
            cout << "Case " << kcase++ << ": ";
            cout << puzzle[0] << ' ' << puzzle[1] << ' ' << puzzle[2] << '\n';
            return true;
        }
        return false;
    }
    int ni = i, nj = j + 1, nd = d;
    if (nj == puzzle[ni].size()) nj = 0, ni++;
    if (ni == 3) return false;
    char cur = puzzle[ni][nj];
    for (char c : cmap) {
        // Skip leading 0
        if (nj == 0 && c == '0') continue;
        if (c != cur) {
            puzzle[ni][nj] = c; 
            if (bt(ni, nj, d + 1, maxd)) return true;
            puzzle[ni][nj] = cur;
        } else {
            // If c == cur, we don't replace the char but just go to next.
            if (bt(ni, nj, d, maxd)) return true;
        }
    }
    return false;
}

int main() {
    string s; 
    while (cin >> s && s[0] != '0') {
      puzzle[0] = s; cin >> puzzle[1]; cin >> puzzle[2];
      // Start from j = -1, because we are going to +1 in the main function.
      for (int maxd = 0;; maxd++) if (bt(0, -1, 0, maxd)) break;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值