UVa 12107 Digit Puzzle 题解

难度:β

建议用时:45 min

实际用时:3 h 30 min

??(你看不出来这是题目链接,对吧?(手动滑稽))

这是我目前为止独立完成的最复杂的一道题。(别喷我太水)

这样一道暴力搜索的题,怎么会花如此多时间?

因为我一直在改细节。

调试了很多。大多数时间都在调试。需要考虑的细节真的蛮多的。

下面分析算法。

这题我看有大神用数组 AC。然而本人因为水平有限,决定用字符串。于是时间肯定要爆。不过我有优化方法,过一会再说。

虽然用了优化,也只是 2500 ms 勉强过。??

题目要求以最少的改动数使得方程只有唯一解。其中两个乘数最多只有 2 位,所以乘出来的数最多有 4 位。

每一位上的数要么是符号 “*”,代表这里可以填任意数,要么是 “0123456789” 中的一个。

于是每一位数只有 11 种可能性。整个方程也只有 11 ^ 8 种可能的排列。

在看主要算法之前,先考虑一下怎样判断一个已知的字符方程是否有且只有唯一解。

为了表示方便,我把两个乘数各自统一为 2 个字符,积统一为 4 个字符。缺的字符用 “#” 代替。

注意不能用 “0”!因为我们要求三个数的长度是一定的,对于超出限制的高位数需要用 “#” 加以区分。

 1 string standarlize(string x, int size) {
 2     string sx = "##";
 3     if (size == 2) {
 4         if (x.length() == 2) sx = x;
 5         if (x.length() == 1) sx[1] = x[0];
 6     } // 2 -> #2
 7
 8     if (size == 4) {
 9         sx += "##";
10         if (x.length() == 1) sx[3] = x[0];
11         if (x.length() == 2) { sx[3] = x[1]; sx[2] = x[0]; }
12         if (x.length() == 3) { sx[3] = x[2]; sx[2] = x[1]; sx[1] = x[0]; }
13         if (x.length() == 4) sx = x;
14     } // 312 -> #312
15
16     return sx;
17 }

然后为了待会儿枚举方便,我把三个数整合成一个 8 位字符数。

xyz += stx; xyz += sty; xyz += stz;

待会用时,可以直接把 xyz 拆开。

好吧。就说我们手上有了这样三个字符数。

“*1” * “#2” = “##**” (注意 “#”  是占位符,不参与计算,数字位数也不能超过这个符号的限制)

这个方程有几个解?

你肯定会脱口而出:4 个!(11 * 2 = 22, 21 * 2 == 42, 31 * 2 == 62, 41 * 2 == 82)

那么你是怎样的出这个答案的呢?

“*1” 里的 “*” 可以取 “1”,“2”,“3”,“4”。

还是用的简单枚举法,对吧?

那么我们就可以用这种方法来球解的个数了。

算法是:枚举前两个乘数,得到一个积。判断两个乘数和积是不是和字符数相匹配。(匹配指诸如 “*2” = “12”,“#2” != “12”,“1234” = “1234” 这类)

(这里省略代码)(~~)

好的。现在知道怎样判断一个给定的方程有没有唯一解了。下面就是看怎样枚举方程了。

方程既然用 8 位字符串表示好了,那么就用 dfs 一个一个依次从左到右试着改变原方程的某一位,从而得到一个不同的方程。

注意:这里枚举要用迭代加深,否则会超时。

 1 bool dfs(int d, int cur) {
 2     if (d == maxd) {
 3         if (one_and_only_solution())
 4             return true;
 5         else
 6             return false;
 7     }
 8
 9     if (cur == 8) return false;
10
11     string cpy = xyz; // 高亮这里
12     for (int i = 0; i <= 10; i++) {
13         if (xyz[cur] == patt[i] || xyz[cur] == ‘#‘) {
14             if (dfs(d, cur+1)) return true;
15         } else {
16             xyz[cur] = patt[i];
17             if (dfs(d+1, cur+1)) return true;
18         }
19         xyz = cpy; // 这里也是
20     }
21
22     return false;
23 }

留心一下 cpy 的作用。否则整个枚举会一团糟。

这题的一个麻烦就在于它给的条件比较多。要搞清楚 “0123456789” “#” “*” 各自的作用才可以把 if 写正确。

下面就只剩下判断是否唯一解了。

这里我开始在枚举乘数的时候是用 0 ~ 99,然而这样花了很多不必要的时间(比如你手上有一个“#1”, 然而你给它枚举了 100 次,每次还判断了是不是匹配的)

如果这里枚举不用优化的话,分分钟超时。(我测过的)

我的想法是在程序执行前就提前建好每一种 2 位字符数匹配的 2 位数字。(注意是“字符串”对应“数字集合”, 自然想到用 map 对应 vector,vector 比 set 方便枚举)

 1 void create_data() {
 2     lib.clear();
 3     string base = "  ";
 4     for (int i = 0; i <= 11; i++)
 5         for (int j = 0; j <= 11; j++) {
 6             base[0] = patt[i]; base[1] = patt[j]; // patt = "*0123456789"
 7             for (int k = 0; k <= 99; k++) {
 8                 if (match(base, k)) {
 9                     lib[base].push_back(k);
10                 }
11             }
12         }
13 }

这样枚举时效率快多了,可以卡进 3 s 时限了。

下面是判断函数。

 1 bool one_and_only_solution() {
 2     string xx = "", yy = "", zz = "";
 3     xx += xyz[0]; xx += xyz[1];
 4     yy += xyz[2]; yy += xyz[3];
 5     zz += xyz[4]; zz += xyz[5];
 6     zz += xyz[6]; zz += xyz[7];
 7
 8     int cnt = 0;
 9     for (int i = 0; i < (int)lib[xx].size(); i++) {
10         for (int j = 0; j <(int)lib[yy].size(); j++) {
11             int v1 = lib[xx][i], v2 = lib[yy][j];
12             int v3 = v1 * v2;
13             if (!compare(zz, v3)) continue;
14             cnt++;
15             if (cnt >= 2) return false; // 必要的优化
16         }
17     }
18
19     return cnt == 1;
20 }

咦,这中间有一个 compare 函数。这是什么?

聪明的你一定能发现,这个函数是用来比较积是否匹配的。

我原本打算把积拆成两半,用两个 2 位数在 vector 里找。然而细思过后觉得不行,于是用下面的笨方法。

 1 bool compare(string cmp, int val) {
 2     if (val == 0) {
 3         if ((cmp[3] == ‘0‘|| cmp[3] == ‘*‘) && cmp[2] == ‘#‘) return true;
 4         return false;
 5     }
 6     string cmp2 = "####";
 7     int iter = 3;
 8     while (val) {
 9         cmp2[iter--] = char(val % 10 + ‘0‘);
10         val /= 10;
11     }
12     for (int i = 0; i <= 3; i++) {
13         if (cmp[i] == ‘*‘ && cmp2[i] != ‘#‘) continue;
14         if (cmp2[i] != cmp[i]) return false;
15     }
16     return true;
17 }

现在所有的都搞定了。

提交爆出 2500 ms。虽然 uDebug上给出了很强的数据,但是评测时显然放松了许多(大概是考虑到我这种弱渣的心情吧)。

2018-01-25

原文地址:https://www.cnblogs.com/Alrond/p/8353358.html

时间: 2024-10-15 17:33:42

UVa 12107 Digit Puzzle 题解的相关文章

UVa 1583 Digit Generator(数)

For a positive integer N , the digit-sum of N is defined as the sum of N itself and its digits. When M is the digitsum of N , we call N a generator of M . For example, the digit-sum of 245 is 256 (= 245 + 2 + 4 + 5). Therefore, 245 is a generator of 

UVa 1225 Digit Counting --- 水题

UVa 1225 题目大意:把前n(n<=10000)个整数顺次写在一起,12345678910111213...,数一数0-9各出现多少字 解题思路:用一个cnt数组记录0-9这10个数字出现的次数,先将cnt初始化为0,接着让i从1枚举到n, 对每个i,处理以活的i的每一个位置上的数,并在相应的cnt下标上+1 最后输出cnt数组即可 /* UVa 1225 Digit Counting --- 水题 */ #include <cstdio> #include <cstring

UVa 10533 - Digit Primes

题目:输出给定区间中,本身是素数,并且这个数的各位之和也是素数的数(称为位素数)的个数. 分析:数论.首先利用筛法,求出1000000内的所有的素数:然后在利用生成的素数表, 判断每个数是不是各位之和也是素数:再后求出从0开始到任意区间中包含位素数数的个数: 最后输出两个区间之差就是区间中的位素数的个数. 说明:达标法计算,查询输出. #include <iostream> #include <cstdlib> #include <cstring> #include &

CodeChef Chef and Digit Jumps 题解

原题链接:Chef and Digit Jumps 题意:原题中有链接. 题解:一道很明显的bfs题,就是跳就可以了,当然,跳的时候可以加一些优化,具体看代码 #include <queue> #include <cstdio> #include <cstring> using namespace std; #define Maxn 100000 char s[Maxn+5]; int n; int a[Maxn+5]; queue<int> q; vect

紫书第三章练习题:UVA 1225 Digit Counting by 15邱盼威

来源:http://m.blog.csdn.net/article/details?id=70861055 Trung is bored with his mathematicshomeworks. He takes a piece of chalk and starts writing a sequence ofconsecutive integers starting with 1 to N (1 < N < 10000). After that, hecounts the number

UVA 11729 Commando War 题解

“Waiting for orders we held in the wood, word from the front never came By evening the sound of the gunfire was miles away Ah softly we moved through the shadows, slip away through the trees Crossing their lines in the mists in the fields on our hand

UVA 1225 Digit Counting(统计数位出现的次数)

Digit Counting Time Limit: 3000MS   Memory Limit: Unknown   64bit IO Format: %lld & %llu SubmitStatus Description Trung is bored with his mathematics homeworks. He takes a piece of chalk and starts writing a sequence of consecutive integers starting

UVa 202 Repeating Decimals 题解

The decimal expansion of the fraction 1/33 is 0.03, where the 03 is used to indicate that the cycle 03 repeats inde?nitely with no intervening digits. In fact, the decimal expansion of every rational number (fraction) has a repeating cycle as opposed

UVa 1225 - Digit Counting - ACM/ICPC Danang 2007 解题报告

1.题目大意 把前n$n\le 10000$个整数顺次写在一起:12345678910111213……计算0~9各出现了多少次. 2.思路 第一想法是打表,然而觉得稍微有点暴力.不过暂时没有想到更好的办法了,写完看了一下其它人的思路好像也差不多是打表的思路. 3.应注意的问题 (1)首先是格式问题,我第一次提交的时候PE了,因为没有意识到空格也会有影响.最开始我的最后一段代码是: for(i=0;i<10;i++) printf("%d ",s[n][i]); printf(&q