PTA团体程序设计天梯赛L2真题讲解L2-037-040

官网https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7

文章目录

    • [L2-037 包装机](#L2-037 包装机)
    • [L2-038 病毒溯源](#L2-038 病毒溯源)
    • [L2-039 清点代码库](#L2-039 清点代码库)
    • [L2-040 哲哲打游戏](#L2-040 哲哲打游戏)


L2-037 包装机

题目分析

本题是数据结构基础模拟题,核心考察队列的典型应用场景:

  • 轨道上的物品遵循「先放置先掉落」的规则,符合队列先进先出的特性;
  • 筐中的物品遵循「最后放入的最先被抓取」的规则,符合栈后进先出的特性;
  • 需要处理两种边界情况:筐满时强制弹出栈顶、轨道/筐为空时操作无效。

解题思路

  1. 数据结构选型
    • queue<char>数组存储每条轨道的物品,数组下标对应轨道编号(1~n);
    • stack<char>存储筐中的物品。
  2. 逐操作模拟
    • 读入操作编号,遇到-1终止循环;
    • 操作0:若栈非空,弹出栈顶元素并直接输出;
    • 操作k (k>0)
      • 若第k条轨道队列为空,跳过本次操作;
      • 若筐已满(栈大小等于最大容量),先弹出栈顶元素输出,腾出空间;
      • 将轨道队首元素压入栈,同时轨道队首出队。

时间复杂度

每个物品最多入队/出队、入栈/出栈各一次,总操作数与物品数量、操作数线性相关,时间复杂度为 O(N),完全满足题目数据范围。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 110;
queue<char> q[N]; // 每条轨道对应一个队列

int main() {
    int n, m, s;
    cin >> n >> m >> s;
    // 读入每条轨道的初始物品
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < m; j++) {
            char c;
            cin >> c;
            q[i].push(c);
        }
    }
    
    stack<char> st; // 筐,用栈存储
    int op;
    while (cin >> op) {
        if (op == -1) break; // 输入结束标志
        if (op == 0) {
            // 0号操作:抓取筐顶物品到流水线
            if (!st.empty()) {
                cout << st.top();
                st.pop();
            }
        } else {
            // 按下对应轨道按钮
            if (q[op].empty()) continue; // 轨道为空,无操作
            // 筐已满,强制先弹出一个物品
            if (st.size() == s) {
                cout << st.top();
                st.pop();
            }
            // 轨道尽头物品落入筐中
            st.push(q[op].front());
            q[op].pop();
        }
    }
    return 0;
}

L2-038 病毒溯源

题目分析

本题是树的深度遍历经典题,核心考察树的存储、最长路径查找、字典序最小路径输出

  • 病毒变异关系构成一棵有根树,每个节点仅有一个父节点,无环,入度为0的节点是病毒源头;
  • 要求找到从根出发的最长变异链,若有多条长度相同的最长链,输出字典序最小的一条。

解题思路

  1. 建树与找根
    • vector<int>数组存储每个节点的子节点,构建邻接表;
    • 统计每个节点的入度,入度为0的节点即为树的根。
  2. 第一次DFS求最长链长度
    • 从根节点出发深度优先遍历,记录路径的最大长度。
  3. 子节点排序保证字典序
    • 将每个节点的子节点按编号从小到大排序,DFS时优先遍历小编号子节点,第一条找到的最长链就是字典序最小的。
  4. 第二次DFS输出路径
    • 用数组记录当前路径,当路径长度等于最长长度时,直接输出路径并终止程序。

时间复杂度

每个节点仅被遍历两次(两次DFS),总时间复杂度为 O(N),满足数据范围要求。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 1e4 + 9;
vector<int> v[N]; // 邻接表,存储每个节点的子节点
int du[N];        // 入度数组,用于查找根节点
int path[N];      // 记录当前搜索路径
int maxLen;       // 最长变异链长度

// 第一次DFS:计算最长链长度
void dfs_len(int u, int len) {
    maxLen = max(maxLen, len);
    for (int son : v[u]) {
        dfs_len(son, len + 1);
    }
}

// 第二次DFS:查找字典序最小的最长链
void dfs_path(int u, int len) {
    path[len] = u;
    if (len == maxLen) {
        // 找到目标路径,直接输出并退出
        for (int i = 1; i <= maxLen; i++) {
            cout << path[i];
            if (i != maxLen) cout << ' ';
        }
        exit(0); // 终止程序,保证第一个找到的就是字典序最小
    }
    for (int son : v[u]) {
        dfs_path(son, len + 1);
    }
}

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        int k, x;
        cin >> k;
        while (k--) {
            cin >> x;
            du[x]++;
            v[i].push_back(x);
        }
    }
    
    // 查找根节点(入度为0)
    int root = 0;
    for (int i = 0; i < n; i++) {
        if (du[i] == 0) {
            root = i;
            break;
        }
    }
    
    // 第一步:求最长链长度
    dfs_len(root, 1);
    cout << maxLen << '\n';
    
    // 子节点升序排序,保证DFS优先走小编号节点
    for (int i = 0; i < n; i++) {
        sort(v[i].begin(), v[i].end());
    }
    
    // 第二步:输出字典序最小的最长链
    dfs_path(root, 1);
    
    return 0;
}

L2-039 清点代码库

题目分析

本题是STL综合应用题,核心考察vector作为映射键、自定义排序规则的使用:

  • 功能相同等价于输出序列完全一致,可用序列作为唯一标识统计出现次数;
  • 输出要求按模块数量降序,数量相同时按输出序列字典序升序。

解题思路

  1. 映射统计频次
    • 利用map<vector<int>, int>统计每个输出序列出现的次数;
    • vector天然支持字典序比较,可直接作为map的键,完全符合题目对序列大小的定义。
  2. 结构化存储与排序
    • 将map中的键值对转为结构体存入vector,方便自定义排序;
    • 重载比较运算符:优先按出现次数降序,次数相同时按序列字典序升序。
  3. 按格式输出
    • 先输出不同功能的总数,再逐行输出次数和对应输出序列。

时间复杂度

  • 插入map的时间为O(N·M logN)(N为模块数,M为每个模块输出个数);
  • 排序时间为O(K logK)(K为不同功能的数量,K≤N);
  • 整体复杂度完全满足题目数据范围。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

// 功能结构体:存储输出序列与对应模块数量
struct Func {
    vector<int> output;
    int cnt;
    // 自定义排序规则
    bool operator < (const Func& other) const {
        if (cnt != other.cnt) {
            return cnt > other.cnt; // 数量多的排在前面
        }
        return output < other.output; // 数量相同,序列字典序小的在前
    }
};

map<vector<int>, int> mp;
vector<Func> ans;

int main() {
    int n, m;
    cin >> n >> m;
    
    // 统计每个功能出现的次数
    for (int i = 0; i < n; i++) {
        vector<int> tmp;
        for (int j = 0; j < m; j++) {
            int x;
            cin >> x;
            tmp.push_back(x);
        }
        mp[tmp]++;
    }
    
    // 将map数据转入vector,便于自定义排序
    for (auto& item : mp) {
        ans.push_back({item.first, item.second});
    }
    
    // 按题目规则排序
    sort(ans.begin(), ans.end());
    
    // 输出结果
    cout << ans.size() << '\n';
    for (auto& f : ans) {
        cout << f.cnt;
        for (int num : f.output) {
            cout << ' ' << num;
        }
        cout << '\n';
    }
    
    return 0;
}

L2-040 哲哲打游戏

题目分析

本题是简单模拟题,核心考察数组模拟、下标偏移处理,属于基础送分题:

  • 剧情点的跳转关系用邻接表存储;
  • 存档功能用数组记录每个档位对应的剧情点;
  • 按顺序模拟所有操作,最终输出终点剧情点。

解题思路

  1. 存储跳转关系
    • vector<int>数组存储每个剧情点的所有选项对应的目标剧情点;
    • 选项编号从1开始,数组下标从0开始,访问时需要做下标减1处理。
  2. 存档数组
    • 用数组记录每个档位存储的剧情点编号,档位从1开始,题目约定不超过100档。
  3. 逐操作模拟
    • 初始当前剧情点为1;
    • 操作0:根据选项号跳转剧情点;
    • 操作1:输出当前剧情点,并将当前剧情点存入对应档位;
    • 操作2:将当前剧情点更新为对应档位的存档内容。
  4. 所有操作结束后,输出最终的当前剧情点。

时间复杂度

每个操作仅执行一次,跳转、存档、读档都是O(1)操作,总时间复杂度为 O(M),效率极高。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 9;
vector<int> plot[N]; // 每个剧情点的跳转选项
int save[105];       // 存档档位,最多100档

int main() {
    int n, m;
    cin >> n >> m;
    
    // 读入每个剧情点的跳转关系
    for (int i = 1; i <= n; i++) {
        int k, x;
        cin >> k;
        while (k--) {
            cin >> x;
            plot[i].push_back(x);
        }
    }
    
    int now = 1; // 当前剧情点,初始为1号
    while (m--) {
        int op, b;
        cin >> op >> b;
        if (op == 0) {
            // 选择第b个选项,跳转剧情
            now = plot[now][b - 1];
        } else if (op == 1) {
            // 存档到第b档,输出当前剧情点
            cout << now << '\n';
            save[b] = now;
        } else if (op == 2) {
            // 读取第b档存档
            now = save[b];
        }
    }
    
    // 输出最终到达的剧情点
    cout << now;
    return 0;
}
相关推荐
charlie1145141911 小时前
IMX6ULL WM8960 的移植——前置介绍
开发语言·c++·开源项目·嵌入式linux
charlie1145141912 小时前
Cinux是如何管理进程的 —— 上下文与调度
开发语言·c++·操作系统·开源项目
龍德明宇2 小时前
如何理解大语言模型的负主体性-龍德明宇
人工智能·算法·大语言模型llm·负主体性·ai存在论
math_hongfan2 小时前
鸿蒙存储异常高级排查:文件损坏检测/数据恢复/读写失败重试/磁盘空间预警系统性根治方案
学习·华为·harmonyos·鸿蒙
hold?fish:palm2 小时前
redis中AOF 重写机制解析
数据库·c++·redis
卡梅德生物科技小能手2 小时前
卡梅德生物科普 Zika Virus(寨卡病毒)|病毒结构与科研实验体系解析
经验分享·深度学习·生活
阿米亚波2 小时前
【C++ 异常处理】try-catch
开发语言·c++·笔记·try-catch
jz_ddk2 小时前
[学习] 深入理解 USB 通信与 FPGA 实现:从协议到逻辑设计
学习·fpga开发·xilinx·usb2.0
白狐_7982 小时前
408数据结构第6章:迪杰斯特拉(Dijkstra)算法——真题精讲
数据结构·算法
旖旎夜光2 小时前
C++(内存管理)
开发语言·c++·学习