PAT甲级 2024年春季考试 攀拓计算机能力测评程序设计 题解

A-1 Braille Recognition

Sample Input:

7 10
..........
.*..**....
..........
..........
.***......
..*.......
..........

Sample Output:

3 0 1 1 0 1 0 0 0 0

解题思路:

这道题是要求我们从输入的盲文(Braille)字符矩阵中识别出数字,并统计不同数字出现的次数。盲文字母是由三个行和两列的点组成的二维矩阵。

以下是我的解题思路:

  1. 数据读取与初始化:首先读取输入的矩阵尺寸和盲文字符矩阵,并设置用于存储结果和盲文模式的变量。

  2. 盲文数字的存储:预先定义每个数字对应的盲文模式。这些模式存储在三维数组 bra 中,第一维表示数字,后一维表示该数字的盲文模式。

  3. 遍历输入矩阵:逐行并按顺序遍历输入的字符矩阵,检查每一个可能的 3x2 子矩阵是否对应某个数字的盲文模式。

  4. 模式匹配:对于每一个 3x2 子矩阵,检查其是否与任一个数字的盲文模式匹配。这里通过双层循环对比子矩阵与盲文模式。在匹配时,如果发现对应位置字符不匹配,跳出循环并继续检查下一个数字的盲文模式。如果完全匹配,则记录该数字出现次数。

  5. 结果输出:最终输出各个数字出现的次数。

我的代码:

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

// 定义盲文数字模式存储的三维数组
int bra[105][105][105];
char num[105][105];
int ans[105];
int n, m;

int main() {
    // 读取输入的矩阵维度
    cin >> n >> m;

    // 设置盲文数字的模式
    // 每个数字的模式存储在三维数组 bra 中
    bra[1][0][0] = 1;
    bra[2][0][0] = 1;
    bra[2][1][0] = 1;
    bra[3][0][0] = 1;
    bra[3][0][1] = 1;
    bra[4][0][0] = 1;
    bra[4][0][1] = 1;
    bra[4][1][1] = 1;
    bra[5][0][0] = 1;
    bra[5][1][1] = 1;
    bra[6][0][0] = 1;
    bra[6][0][1] = 1;
    bra[6][1][0] = 1;
    bra[7][0][0] = 1;
    bra[7][0][1] = 1;
    bra[7][1][0] = 1;
    bra[7][1][1] = 1;
    bra[8][0][0] = 1;
    bra[8][1][0] = 1;
    bra[8][1][1] = 1;
    bra[9][0][1] = 1;
    bra[9][1][0] = 1;
    bra[0][0][1] = 1;
    bra[0][1][0] = 1;
    bra[0][1][1] = 1;

    // 读取盲文字符矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> num[i][j];

    // 遍历输入的字符矩阵
    int f = 0, ff = 0;
    for (int k1 = 0; k1 <= n - 3; k1++) {
        for (int k2 = 0; k2 <= m - 2; k2++) {
            int now = 0;
            while (now <= 9) {
                f = 0;
                for (int i = 0; i < 3; i++) {
                    for (int j = 0; j < 2; j++) {
                        // 检查当前 3x2 子矩阵与盲文模式是否匹配
                        if ((num[k1 + i][k2 + j] == '*' && bra[now][i][j] == 1) ||
                            (num[k1 + i][k2 + j] == '.' && bra[now][i][j] == 0)) {
                            // 匹配的情况,继续检查下一个位置
                        } else {
                            f = 1;    // 不匹配,跳出循环
                        }
                    }
                }
                if (f == 1) {
                    now++;  // 如果不匹配,检查下一个数字的盲文模式
                } else {
                    ans[now]++; // 如果匹配,记录该数字的出现次数
                    break;
                }
            }
        }
    }

    // 输出结果
    for (int i = 1; i <= 9; i++)
        cout << ans[i] << " ";
    cout << ans[0];

    return 0;
}

一时没想到好的方法,只能硬模拟,敲得时间比较长,仅作参考,好在最后没出问题。

A-2 AI Comments

Sample Input:

5 3
A20190289 98 74 35 88 60
B20018372 86 86 86 72 60
A19873001 62 48 55 20 35
T27346900 76 54 68 81 70
B00247834 92 68 78 50 66
T27346900
F19993001
B20018372

Sample Output:

5 4 3 -1 -2
Not Found
2 3 1 4 5

解题思路:

这道题要求我们处理一组考生的5个能力指标,并根据这些指标计算各个中位数,然后根据提供的查询输出每个考生的反馈情况。具体来讲,需要把每个考生的指标与对应的中位数进行比较,将其划分为正反馈类或负反馈类,按特定顺序输出这些反馈。以下是具体的解题思路:

  1. 数据读取与初始化
    • 从输入中读取考生数量 和查询数量。
    • 初始化数据结构来存储每个考生的信息(包括其注册ID和5个指标)。
  2. 计算中位数
    • 对每个指标,单独存储所有考生的该指标值。
    • 对每个指标数组进行排序,然后根据奇偶情况计算中位数。
  3. 处理查询
    • 对每个查询,检查所查询的注册ID是否存在。
    • 如果不存在,输出 "Not Found"。
    • 如果存在,比对该考生的指标与中位数,将其归类为正反馈或负反馈。
    • 按要求的排序规则输出反馈。

我的代码:

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

// 定义结构体存储每个考生的信息,包括注册ID和5个能力值
struct reg {
    string id;
    int v[10];
} exam[100005];

// 用于存储每个指标所有考生的数值
vector<int> vi[10];

// 中位数数组
int media[10];

// 存储考生数量和查询数量
int n, m;

// 存储正反馈和负反馈的指标值
int big[100005];

// 用注册ID来快速找到对应考生的索引
map<string, int> mp;

// 存储排序好的指标差异,用于生成输出反馈
vector<pair<int, int>> pos;

// 排序函数,用于从小到大排序
bool cmp(int a, int b) {
    return a < b;
}

// 排序函数,用于从大到小排序,如果相等按维度从小到大
bool cmp2(pair<int, int> a, pair<int, int> b) {
    if (a.first == b.first)
        return a.second < b.second;
    return a.first > b.first;
}

int main() {
    // 读入考生数量和查询数量
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> exam[i].id; // 读入考生注册ID
        mp[exam[i].id] = i; // 存入map中用于快速查找
        for (int j = 1; j <= 5; j++) {
            cin >> exam[i].v[j]; // 读入考生5个能力值
            vi[j].push_back(exam[i].v[j]); // 存入各指标数组中
        }
    }

    // 计算每个指标的中位数
    for (int i = 1; i <= 5; i++) {
        sort(vi[i].begin(), vi[i].end(), cmp); // 对每个指标数组排序
        if (n % 2 == 1)
            media[i] = vi[i][(n + 1) / 2 - 1]; // 奇数情况下的中位数
        else
            media[i] = vi[i][n / 2]; // 偶数情况下的中位数
        // cout << media[i] << endl; // 输出中位数用于调试
    }

    // 处理每个查询
    while (m--) {
        string s;
        cin >> s; // 读取查询的注册ID
        if (mp[s] == 0) {
            cout << "Not Found" << endl; // 如果ID不存在,输出Not Found
            continue;
        }
        pos.clear(); // 清空pos
        for (int i = 1; i <= 5; i++)
            big[i] = 0; // 初始化big数组

        // 将该考生的指标归类为正反馈或负反馈
        for (int i = 1; i <= 5; i++) {
            if (exam[mp[s]].v[i] >= media[i]) {
                big[i] = 1; // 记录正反馈类
                pos.push_back({exam[mp[s]].v[i] - media[i], i}); // 计算差异并存入pos
            } else {
                pos.push_back({exam[mp[s]].v[i] - media[i], i}); // 负反馈同样存入pos
            }
        }

        // 对pos进行排名
        sort(pos.begin(), pos.end(), cmp2);
        
        // 输出反馈信息
        for (int i = 0; i < pos.size(); i++) {
            if (i != 0)
                cout << " "; // 控制输出格式
            if (big[pos[i].second] == 0) {
                cout << '-'; // 负反馈前加负号-
            }
            cout << pos[i].second; // 输出维度编号
        }
        if (m != 0)
            cout << endl; // 控制换行
    }
    
    return 0;
}

 这种题很容易写错,如果写错了就很难发现。

A-3 Degree of Skewness

 

Sample Input:

7
1 2 7 5 4 3 6
1 2 3 4 7 5 6

Sample Output:

2 = 3 - 1

解题思路

给定一个二叉树,我们需要计算其“偏度”(Degree of Skewness),定义为只有左子节点的节点数与只有右子节点的节点数之差。给定输入包括二叉树的后序遍历和中序遍历,通过它们我们可以构建二叉树,然后统计所需节点数。

核心步骤如下:

  1. 数据读取

    • 读取节点数量 。
    • 读取后序遍历和中序遍历序列。
  2. 构建二叉树

    • 使用递归的方法通过后序和中序遍历序列构建二叉树。
  3. 统计节点情况

    • 通过遍历二叉树,统计只有左子节点的节点数和只有右子节点的节点数。
    • 计算偏度。
  4. 输出结果

    • 按指定格式输出偏度及其组成部分。

我的代码

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

// 定义节点数据结构
struct node {
    int lchild, rchild; // 左子节点和右子节点
} t[10005];

int n;  // 节点数量
int post[1005], in[1005], inp[1005]; // 后序遍历、中序遍历及其索引映射
int nl, nr, ds; // 记录只有左子节点、只有右子节点的计数,以及偏度

// 从后序和中序遍历构建二叉树
int build(int il, int ir, int pl, int pr) {
    if (il > ir) return -1; // 递归边界:无效区间

    int node = post[pr]; // 取后序遍历区间的末尾作为根节点
    // 构建左子树
    t[node].lchild = build(il, inp[node] - 1, pl, pl + inp[node] - il - 1);
    // 构建右子树
    t[node].rchild = build(inp[node] + 1, ir, pr - (ir - inp[node]), pr - 1);

    return node; // 返回根节点
}

int main() {
    // 输入节点数量
    cin >> n;

    // 输入后序遍历序列
    for (int i = 1; i <= n; i++)
        cin >> post[i];
    
    // 输入中序遍历序列,并建立索引映射
    for (int i = 1; i <= n; i++) {
        cin >> in[i];
        inp[in[i]] = i;
    }

    // 构建二叉树
    build(1, n, 1, n);

    // 遍历整个树的每个节点,统计只有左子节点和只有右子节点的数量
    for (int i = 1; i <= n; i++) {
        if (t[i].lchild == -1 && t[i].rchild != -1)
            nr++; // 只有右子节点的节点计数
        else if (t[i].lchild != -1 && t[i].rchild == -1)
            nl++; // 只有左子节点的节点计数
    }

    // 计算偏度
    ds = nl - nr;
    
    // 打印结果
    printf("%d = %d - %d", ds, nl, nr);

    return 0;
}

比较常规的题

A-4 Uniqueness of Topological Order

Sample Input 1:

6 9
2 1
1 3
5 2
5 4
2 3
2 6
3 4
6 4
6 1

Sample Output 1:

5
Yes
5 2 6 1 3 4

Sample Input 2:

6 8
1 2
1 3
5 2
5 4
2 3
2 6
3 4
6 4

Sample Output 2:

1 5
No

解题思路:

这道题要求检查一个给定的有向图是否存在唯一的拓扑序列。我们需要做以下几件事:

  1. 读取输入

    • 读取顶点数和边数。
    • 然后读取所有有向边。
  2. 计算入度

    • 计算每个顶点的入度(有多少条边指向这个顶点)。
  3. 查找最小入度的顶点

    • 找出入度最小的顶点。
    • 如果存在多个入度为零的顶点,则不可能存在唯一拓扑排序。
  4. 拓扑排序

    • 使用拓扑排序算法(深度优先搜索)。
    • 在排序过程中,如果某一时刻有多个顶点可以选择,则不可能存在唯一的拓扑序列。
  5. 输出结果

    • 根据拓扑排序结果,输出是否存在唯一的拓扑排序及相应的顺序。

我的代码:

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

int n, m; // 顶点和边的数量
int in[100005], ind[100005], vis[10005]; // 入度数组, 复制的入度数组, 访问标记
vector<int> v[100005]; // 邻接表
vector<int> ans, temp, num; // 最终答案临时存储, 临时访问路径存储, 入度最小顶点存储

// 拓扑排序递归函数
void topo(int node, int num) {
    temp.push_back(node); // 将当前节点加入临时路径
    vis[node] = 1; // 标记该节点已访问

    if (num == n) { // 递归边界条件:所有节点都已访问
        if (!ans.empty()) { // 如果已有合法路径,则说明拓扑排序结果不唯一
            cout << "No" << endl;
            exit(0);
        } else {
            ans = temp; // 如果没有合法路径,则记录当前路径
        }
        temp.pop_back(); // 回溯:移除当前节点
        vis[node] = 0; // 回溯:取消访问标记
        return;
    }

    // 遍历当前节点的所有邻接节点,减少其入度
    for (int i = 0; i < v[node].size(); i++) {
        in[v[node][i]]--;
    }

    // 尝试进行拓扑排序
    for (int i = 0; i < v[node].size(); i++) {
        if (in[v[node][i]] == 0) {
            topo(v[node][i], num + 1); // 递归处理入度为0的节点
        }
    }

    // 回溯:恢复邻接节点的入度
    for (int i = 0; i < v[node].size(); i++) {
        in[v[node][i]]++;
    }

    vis[node] = 0; // 回溯:取消访问标记
    temp.pop_back(); // 回溯:移除当前节点
}

int main(){
    int minn = 1e9; // 最小入度初始为无穷大
    cin >> n >> m; // 输入顶点和边的数量
    for (int i = 1; i <= m; i++) {
        int start, des;
        cin >> start >> des; // 输入每条边
        v[start].push_back(des); // 建立邻接表
        in[des]++; // 更新终点的入度
    }

    // 找出所有入度最小的顶点
    for (int i = 1; i <= n; i++) {
        if (in[i] < minn) {
            minn = in[i];
            num.clear();
            num.push_back(i);
        } else if (in[i] == minn) {
            num.push_back(i);
        }
    }

    // 输出所有最小入度的顶点,按升序排列
    for (int i = 0; i < num.size() - 1; i++) {
        cout << num[i] << " ";
    }
    cout << num[num.size() - 1] << endl;

    // 如果存在且仅有一个入度为0的顶点,进行拓扑排序
    if (in[num[0]] == 0 && num.size() == 1) {
        topo(num[0], 1);
        
        if (!ans.empty()) { // 如果存在合法的拓扑排序路径
            cout << "Yes" << endl;
            for (int i = 0; i < ans.size() - 1; i++) {
                cout << ans[i] << " ";
            }
            cout << ans[ans.size() - 1];
        } else {
            cout << "No" << endl;
        }
    } else {
        cout << "No" << endl;
    }
}

把最短路时找路径的方法用在拓扑排序上就行

  • 29
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值