按列拆解 N 进制加法竖式,从最高位向低位 DFS,用 0/1 进位链反解入进位并即时剪枝,直接得到唯一解。
OJ: roj
题目 ID: 3535
难度:提高
标签:
创建: 2026-10-02 06:04
更新: 2026-10-10 11:30
形式化题目
给定
其中
正解
思路
朴素做法:把前
按列拆解。竖式逐列来看,第
两个关键观察:
- 进位只有
或 :两个 进制数相加最大为 ,所以任何一列的出进位都不超过 。 - 和也是
位,最高位出进位固定为 ;个位的入进位固定为 。于是整条进位链两端都已知。
搜索方向。若从个位向高位搜,出进位要等低位填完才知道;反过来从最高位向低位搜,出进位一开始就是已知的(最高位列是
它恰好就是下一列(更低一位)的出进位——进位链沿着搜索方向一路传到个位,最后在个位验证
每列的枚举。一列最多涉及
- 三个字母都已确定:直接验证等式,只试两种入进位,不建任何枚举结构;
- 把已确定的字母折进常数项,方程化为
,其中加数位字母系数 、结果位字母系数 ,同一字母两边出现则抵消为 ; - 留一个系数非零的字母最后由方程唯一解出(要求整除、落在
、且没被认领),其余字母逐一枚举,枚举维度少一维; - 系数被抵消的字母方程管不到,直接从空闲数字中枚举,但等式必须先成立,否则整个进位组合直接剪掉。
这样每列只枚举仍空闲的数字,靠"等式成立 + 进位是
样例(
| 列(低位→高位) | 加数 1 | 加数 2 | 和 | 等式 | 出进位 |
|---|---|---|---|---|---|
| 第 0 列 | |||||
| 第 1 列 | |||||
| 第 2 列 | |||||
| 第 3 列 | |||||
| 第 4 列 |
最高位出进位为
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int cola[30], colb[30], colc[30]; // 每列三个字母(低位在前)
int val[30]; // 字母 -> 数字,-1 未定
bool used[30];
int ans[30];
// 给本列未定字母 letters 分配数字,验证等式 Σ coeff[l]*val[l] == rhs 后递归下一列
bool assign_letters(const vector<int>& letters, const int coeff[30], int idx, int rhs, int pos, int carry_in);
// 从最高位列 pos 向低位搜;carry_out 是本列出进位(最高位固定 0)
bool dfs(int pos, int carry_out) {
if (pos < 0) {
for (int i = 0; i < n; i++) ans[i] = val[i];
return true;
}
int a = cola[pos], b = colb[pos], c = colc[pos];
int va = val[a], vb = val[b], vc = val[c];
int nco = n * carry_out;
int lo = 0;
int hi = (pos == 0) ? 0 : 1;
// 三个字母都已定:等式必须对某个入进位成立
if (va >= 0 && vb >= 0 && vc >= 0) {
int base = va + vb - vc;
for (int carry_in = lo; carry_in <= hi; carry_in++)
if (base == nco - carry_in && dfs(pos - 1, carry_in)) return true;
return false;
}
// 本列未定字母的系数;已定字母折进常数项 base
int coeff[30];
memset(coeff, 0, sizeof(coeff));
bool appear[30] = {false};
int base = 0;
if (va < 0) { coeff[a] += 1; appear[a] = true; } else base += va;
if (vb < 0) { coeff[b] += 1; appear[b] = true; } else base += vb;
if (vc < 0) { coeff[c] -= 1; appear[c] = true; } else base -= vc;
vector<int> letters;
for (int l = 0; l < n; l++)
if (appear[l]) letters.push_back(l);
for (int carry_in = lo; carry_in <= hi; carry_in++) {
int rhs = nco - carry_in - base;
if (assign_letters(letters, coeff, 0, rhs, pos, carry_in)) return true;
}
return false;
}
bool assign_letters(const vector<int>& letters, const int coeff[30], int idx, int rhs, int pos, int carry_in) {
if (idx == (int)letters.size()) {
int got = 0;
for (size_t li = 0; li < letters.size(); li++)
got += coeff[letters[li]] * val[letters[li]];
if (got == rhs) return dfs(pos - 1, carry_in);
return false;
}
int l = letters[idx];
for (int d = 0; d < n; d++) {
if (used[d]) continue;
used[d] = true;
val[l] = d;
if (assign_letters(letters, coeff, idx + 1, rhs, pos, carry_in)) return true;
val[l] = -1;
used[d] = false;
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
string s1, s2, s3;
cin >> s1 >> s2 >> s3;
reverse(s1.begin(), s1.end());
reverse(s2.begin(), s2.end());
reverse(s3.begin(), s3.end());
for (int i = 0; i < n; i++) {
cola[i] = s1[i] - 'A';
colb[i] = s2[i] - 'A';
colc[i] = s3[i] - 'A';
}
for (int i = 0; i < n; i++) { val[i] = -1; used[i] = false; }
dfs(n - 1, 0);
for (int i = 0; i < n; i++) {
if (i) cout << " ";
cout << ans[i];
}
cout << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 06:07
# update_at: 2026-10-02 06:30
import sys
from itertools import permutations
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
n = int(next(data))
# 三行都按低位在前重排列:从最高位往低位搜索,出进位先已知(最高位固定为 0),
# 本列的入进位由等式反解出来,恰好等于下一列的出进位,进位链因此一路传到个位。
cols = list(
zip(*([ord(ch) - 65 for ch in next(data).decode()[::-1]] for _ in range(3)))
)
val = [-1] * n # 字母 -> 数字;-1 表示还没定
used = [False] * n # 数字是否已被认领
ans: list[int] = []
# 热路径变量走形参默认值:递归调用点少传参,闭包变量也变成本地变量
def dfs(pos: int, carry_out: int, cols=cols, n=n, val=val, used=used) -> bool:
"""从最高位列 pos 向低位搜(carry_out 是本列出进位),进位链接回个位且无多余进位时返回 True。"""
if pos < 0:
ans[:] = val # 每列等式都成立、进位链两端都是 0,得到唯一解
return True
a, b, c = cols[pos]
v_a, v_b, v_c = val[a], val[b], val[c]
carries = (0,) if pos == 0 else (0, 1) # 个位的入进位必为 0
n_co = n * carry_out
# 三个字母都已定:等式必须对某个入进位成立,直接验证,不建任何枚举结构
if v_a >= 0 and v_b >= 0 and v_c >= 0:
base = v_a + v_b - v_c
for carry_in in carries:
if base == n_co - carry_in and dfs(pos - 1, carry_in):
return True
return False
# 本列未定字母的系数:出现在加数位记 +1,出现在结果位记 -1,两边都出现则抵消成 0;
# 已定字母折进常数项 base,方程化为 Σ 系数×字母 = n*出进位 - 入进位 - base
coeff: dict[int, int] = {}
base = 0
for ch, vc in ((a, v_a), (b, v_b)):
if vc < 0:
coeff[ch] = coeff.get(ch, 0) + 1
else:
base += vc
if v_c < 0:
coeff[c] = coeff.get(c, 0) - 1
else:
base -= v_c
pivots = [l for l, w in coeff.items() if w] # 方程能解出的字母
zero = [l for l, w in coeff.items() if not w] # 被抵消的字母:本列方程管不到
# 没有可解字母(未定字母全被抵消):等式必须直接成立,否则先剪掉该进位组合
if not pivots:
for carry_in in carries:
if n_co - carry_in != base:
continue
for combo in permutations([d for d in range(n) if not used[d]], len(zero)):
for l, d in zip(zero, combo):
val[l] = d
used[d] = True
if dfs(pos - 1, carry_in):
return True
for l, d in zip(zero, combo):
val[l] = -1
used[d] = False
return False
# 只留一个字母最后解方程,枚举维度少一维
pivot = pivots.pop()
free = zero + pivots
wp = coeff[pivot]
rest = [d for d in range(n) if not used[d]]
for carry_in in carries:
rhs = n_co - carry_in - base
for combo in permutations(rest, len(free)):
got = 0
for l, d in zip(free, combo):
w = coeff[l]
if w:
got += w * d
rem = rhs - got
if rem % wp:
continue
digit = rem // wp # 由方程唯一解出的最后一个字母
if not 0 <= digit < n or used[digit] or digit in combo:
continue
val[pivot] = digit
used[digit] = True
for l, d in zip(free, combo):
val[l] = d
used[d] = True
if dfs(pos - 1, carry_in): # 本列入进位就是下一列的出进位
return True
for l, d in zip(free, combo): # 回溯:撤销本列的赋值
val[l] = -1
used[d] = False
val[pivot] = -1
used[digit] = False
return False
dfs(n - 1, 0)
print(' '.join(map(str, ans)))
if __name__ == "__main__":
solve()复杂度
设搜索树节点数为
总结
把竖式按列拆开、把进位当作