【例题3】虫食算
题面
题目描述
所谓虫食算,就是原先的算是中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定啃掉的字母。来看一个简单的例子:
43#9865#045
+8468#6633
44445509678
其中 #
号表示被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是 \(5\) 和 \(3\),第二行的数字是 \(5\)。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是 \(n\) 进制加法,算式中三个数都有 \(n\) 位,允许有前导的 \(0\)。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是 \(n\) 进制的,我们就取英文字母表的前 \(n\) 个大写字母来表示这个算式中的 \(0\) 到 \(n-1\) 这 \(n\) 个不同的数字;但是这 \(n\) 个字母并不一定顺序地代表 \(0\) 到 \(n-1\)。输入数据保证 \(n\) 个字母分别至少出现一次。
BADC
+CBDA
DCCC
上面的算式是一个 \(4\) 进制的算式。很显然,我们只要让 ABCD 分别代表 \(0123\),便可以让这个式子成立了。你的任务是,对于给定的 \(n\) 进制加法算式,求出 \(n\) 个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
输入格式
输入的第一行是一个整数 \(n\),代表进制数。
第二行到第四行,每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这 \(3\) 个字符串左右两端都没有空格。从左到右一次代表从高位到低位,并且恰好有 \(n\) 位。
输出格式
输出一行 \(n\) 个用空格隔开的整数,分别表示 \(A,B,...\) 代表的数字。
样例
样例输入
5
ABCED
BDACE
EBBAA
样例输出
1 0 3 4 2
数据范围与提示
对于 \(30\%\) 的数据,保证 \(n\leq 10\)。
对于 \(50\%\) 的数据,保证 \(n\leq 15\)。
对于 \(100\%\) 的数据,保证 \(1\leq n\leq 26\)。
分析
- 首先,题意说明我们需要模拟运算流程,通过加法运算的流程正确性来判断是否成立。
- 然后呢,没有然后了。
- 本题可以爆搜,也可以用高斯消元
(但是我不会) - 对于爆搜,可以枚举各个字母所代表的数字,在加上加法式子,从而得到答案。
- 对于加法式子的判断,如果一列上的加数为 a,b,和为 c,进位数为 x,有如下几点
- 如果进位数 x 是确定的,那么 \((a + b + x) \% n == c\).
- 如果进位数 x 是不确定的,也就是 x 可能为 \(0\),也可能为 \(1\),那么 \((a+b)\%n==c\) 或者是 \((a + b + 1) \%n == c\).
- 对于最高位,不仅要满足 \((a + b + c) \%n == c\) 而且要满足 \((a + b + c) / n ==0\).
- 但是单纯这样的话会超时,所以我们枚举字母从式子的第 \(1\) 位到第 \(n\) 位的顺序来枚举,可以加快搜索的结束。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n;
string s[3];
int s1[N], s2[N], s3[N];
int number[N];
int p[N], cnt;
bool vis[N], used[N];
bool check() {
int x = 0;
for (int i = n - 1; i >= 0; --i) {
int a = number[s1[i]];
int b = number[s2[i]];
int c = number[s3[i]];
if (a != -1 && b != -1 && c != -1) {
if (x != -1) {
if ((a + b + x) % n != c)
return false;
if (!i && (a + b + x) / n != 0)
return false;
x = (a + b + x) / n;
}
else {
if ((a + b) % n != c && (a + b + 1) % n != c)
return false;
if (!i && (a + b) / n != 0)
return false;
}
}
else x = -1;
}
return true;
}
bool dfs(int x) {
if (x == cnt)
return true;
for (int i = 0; i < n; ++i) {
if (!vis[i]) {
number[p[x]] = i;
vis[i] = true;
if (check() && dfs(x + 1))
return true;
number[p[x]] = -1;
vis[i] = false;
}
}
return false;
}
int main(void) {
memset(number, -1, sizeof(number));
cin >> n;
for (int i = 0; i < 3; ++i) cin >> s[i];
for (int i = 0; i < n; ++i) {
s1[i] = s[0][i] - 'A';
s2[i] = s[1][i] - 'A';
s3[i] = s[2][i] - 'A';
}
for (int i = n - 1; i >= 0; --i) {
if (!used[s1[i]]) p[cnt++] = s1[i], used[s1[i]] = 1;
if (!used[s2[i]]) p[cnt++] = s2[i], used[s2[i]] = 1;
if (!used[s3[i]]) p[cnt++] = s3[i], used[s3[i]] = 1;
}
dfs(0);
for (int i = 0; i < n; ++i)
cout << number[i] << " ";
}