【例题3】虫食算

【例题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] << " ";
}
上一篇:51nod3063 小明爱正方形


下一篇:Monorepo与multirepo区别何在?为什么大公司像谷歌.微软.优步.Neflix.Nike都在Monorepo?