题解:洛谷 P1013 进制位

【题目来源】

洛谷:P1013 [NOIP 1998 提高组] 进制位 - 洛谷

【题目描述】

著名科学家卢斯为了检查学生对进位制的理解,他给出了如下的一张加法表,表中的字母代表数字。例如:

\[\def\arraystretch{2} \begin{array}{c||c|c|c|c} \rm + & \kern{.5cm} \rm \mathclap{L} \kern{.5cm} & \kern{.5cm} \rm \mathclap{K} \kern{.5cm} & \kern{.5cm} \rm \mathclap{V} \kern{.5cm} & \kern{.5cm} \rm \mathclap{E} \kern{.5cm} \\ \hline\hline \rm L & \rm L & \rm K & \rm V & \rm E \\ \hline \rm K & \rm K & \rm V & \rm E & \rm \mathclap{KL} \\ \hline \rm V & \rm V & \rm E & \rm \mathclap{KL} & \rm \mathclap{KK} \\ \hline \rm E & \rm E & \rm \mathclap{KL} & \rm \mathclap{KK} & \rm \mathclap{KV} \\ \end{array} \]

其含义为:

\(L+L=L\)\(L+K=K\)\(L+V=V\)\(L+E=E\)

\(K+L=K\)\(K+K=V\)\(K+V=E\)\(K+E=KL\)

\(\cdots\)

\(E+E=KV\)

根据这些规则可推导出:\(L=0\)\(K=1\)\(V=2\)\(E=3\)

同时可以确定该表表示的是 \(4\) 进制加法。

【输入】

第一行一个整数 \(n\)\(3\le n\le9\))表示行数。

以下 \(n\) 行,每行包括 \(n\) 个字符串,每个字符串间用空格隔开。

若记 \(s_{i,j}\) 表示第 \(i\) 行第 \(j\) 个字符串,数据保证 \(s_{1,1}=\texttt +\)\(s_{i,1}=s_{1,i}\)\(|s_{i,1}|=1\)\(s_{i,1}\ne s_{j,1}\)\(i\ne j\))。

保证至多有一组解。

【输出】

第一行输出各个字母表示什么数,格式如:L=0 K=1 \(\cdots\) 按给出的字母顺序排序。不同字母必须代表不同数字。

第二行输出加法运算是几进制的。

若不可能组成加法表,则应输出 ERROR!

【输入样例】

5
+ L K V E
L L K V E
K K V E KL
V V E KL KK
E E KL KK KV

【输出样例】

L=0 K=1 V=2 E=3
4

【核心思想】

  1. 问题分析:给定一个 \(n \times n\) 的加法表,第一行和第一列为 \(n-1\) 个不同字母(代表数字),表内是这些字母相加的结果(可能是一位或两位字母)。需要确定每个字母代表的数字(0 到 \(n-2\))以及该加法表的进制(\(n-1\) 进制)。若不存在合法映射,输出 ERROR!。本质是一个进制加法表验证与映射问题,利用加法表的性质(如某个字母的进位规律)可以推导出映射关系。

  2. 算法选择

    • 推导映射:在 \(k\) 进制加法中,一个字母若在加法结果中多次出现在十位(即作为两位数的第一位),则它代表的数字通常为 1。通过观察加法表,可以找到代表数字 0 和 1 的字母,然后利用加法规则推出其他字母的映射。但代码中使用了一种基于“关键字母”的推导方法,复杂度 \(O(n^2)\)
    • 验证:得到映射后,遍历表内所有加法项,检查是否满足 (字母值 + 字母值) == 结果值(按 \(k\) 进制计算)。
  3. 关键步骤(基于代码逻辑):

    • 读入:读取 \(n\)\(n \times n\) 个字符串。
    • 推导映射
      • 进制 \(k = n-1\)
      • 找到第一个出现在两位数十位上的字母 c[1](即进位标志)。
      • 找到对应位置的行列,确定数字 0 对应的字母 c[0]
      • 逐步推导出所有字母的数字映射。
    • 验证:对每个加法项 s[i][j],计算 find(s[i][1][0]) + find(s[1][j][0]),与解析 s[i][j] 得到的值(按 \(k\) 进制)比较。若全部相等,则映射合法。
    • 输出:按第一行的顺序输出映射,再输出进制 \(k\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2)\)
    • 空间复杂度:\(O(n^2)\)
  5. 进制加法表推导

    • 进制确定\(n-1\) 个不同字母表示 \(0\)\(n-2\),因此进制为 \(n-1\)
    • 进位规律:在加法中,若结果长度大于 1,则高位的字母通常代表 1(因为最大进位是 1)。
    • 验证必要性:推导出的映射可能不唯一或错误,必须通过遍历表验证。
    • 适用场景:这类“进制加法表”问题,是模拟与数学推导的结合,常见于 NOIP 提高组。

【算法标签】

普及+ #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int n;              // 矩阵的大小 n x n
int k;              // 进制数 k = n - 1
int row, col;       // 用于记录关键行和列的位置
string s[15][15];   // 存储输入的矩阵
char c[15];         // 存储数字到字符的映射关系

/**
 * 查找字符在映射数组中的索引
 * @param x 要查找的字符
 * @return 字符对应的索引值
 */
int find(char x)
{
    int res = 0;
    for (int i = 0; i < k; i++)
    {
        if (c[i] == x)
        {
            res = i;
            break;
        }
    }
    return res;
}

/**
 * 检查矩阵是否满足加法运算规则
 * @return 如果矩阵满足规则返回true,否则false
 */
bool check()
{
    // 检查矩阵中每个位置是否满足加法规则
    for (int i = 2; i <= n; i++)
    {
        for (int j = 2; j <= n; j++)
        {
            // 计算期望值:行首 + 列首
            int x = find(s[i][1][0]) + find(s[1][j][0]);
            int y = 0;

            // 解析矩阵中的实际值
            if (s[i][j].size() == 1)
            {
                // 一位数的情况
                y = find(s[i][j][0]);
            }
            else
            {
                // 两位数的情况:高位*k + 低位
                y = find(s[i][j][0]) * k + find(s[i][j][1]);
            }

            // 检查期望值是否等于实际值
            if (x != y)
            {
                return false;
            }
        }
    }
    return true;
}

int main()
{
    // 输入矩阵大小
    cin >> n;
    k = n - 1;  // 进制数为n-1

    // 输入矩阵数据
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> s[i][j];
            // 如果单元格是两位数,记录第一个字符作为关键字符
            if (s[i][j].size() == 2)
            {
                c[1] = s[i][j][0];
            }
        }
    }

    // 在第一行中找到包含关键字符的列
    for (int i = 2; i <= n; i++)
    {
        if (s[1][i][0] == c[1])
        {
            col = i;  // 记录关键列
            break;
        }
    }

    // 在关键列中找到包含关键字符的行
    for (int i = 2; i <= n; i++)
    {
        if (s[i][col].size() == 1 && s[i][col][0] == c[1])
        {
            row = i;  // 记录关键行
            c[0] = s[row][i][0];  // 记录0对应的字符
            break;
        }
    }

    // 构建完整的字符映射表
    for (int i = 1; i <= k - 2; i++)
    {
        // 找到当前字符对应的行
        for (int j = 2; j <= n; j++)
        {
            if (c[i] == s[j][1][0])
            {
                row = j;
                break;
            }
        }
        // 记录下一个字符
        c[i + 1] = s[row][col][0];
    }

    // 验证矩阵的正确性
    if (check() == false)
    {
        cout << "ERROR!" << endl;
        return 0;
    }

    // 输出字符到数字的映射关系
    for (int i = 2; i <= n; i++)
    {
        printf("%c=%d ", s[1][i][0], find(s[1][i][0]));
    }
    cout << endl << k << endl;

    return 0;
}

【运行结果】

5
+ L K V E
L L K V E
K K V E KL
V V E KL KK
E E KL KK KV
L=0 K=1 V=2 E=3
4
posted @ 2026-09-01 09:25  团爸讲算法  阅读(14)  评论(0)    收藏  举报