2025长亭校招笔试
❌网络接口配置变更
Description
网络接口代表了系统与网络之间的一个连接点, 某个系统支持三种类型的网络接口:
物理接口(Physical Interface): 这是网卡上实际存在的网络接口.
链路聚合接口(Bond Interface):这是将多个物理接口捆绑在一起构成的逻辑接口. 通过链路聚合, 可以将多个物理网络连接捆绑成一个逻辑接口, 从而提高网络性能和可靠性. 链路聚合口所捆绑的物理接口叫做他的子接口.
VLAN 接口(VLAN Interface):这是在一个物理接口或一个链路聚合接口的基础上创建的逻辑接口. 它允许在同一物理网络上分离不同 VLAN 的网络流量. 用来创建 VLAN 接口的物理接口或链路聚合接口称为它的父接口.
三类接口的属性:
物理接口: 名称
链路聚合接口: 名称, 子接口
VLAN 接口: 名称, 父接口
该系统的网络接口需符合以下约束:
所有接口的名称不可重复.
物理接口不会发生变化, 即它们的数量和名称是固定的.
Bond 接口的子接口只能是物理接口, 子接口数量大于或等于 1.
一个物理接口只能是一个 Bond 接口的子接口, 不能同时属于两个 Bond 接口,也不能既是一个 Bond 接口的子接口又是一个 VLAN 接口的父接口.
多个 VLAN 接口可以有相同的父接口.
VLAN 接口的父接口可以是物理接口或 Bond 接口, 不可是其它 VLAN 接口.
创建 VLAN 接口时,其父接口必须已经存在; 当一个接口是某个 VLAN 接口的父接口时,该接口不可被删除.
系统支持四种操作: 创建 Bond 接口, 删除 Bond 接口, 创建 VLAN 接口, 删除 VLAN 接口.
本题输入系统当前的网络接口配置, 和目标的网络接口配置, 请输出最少的变更步骤数 K,使得系统能通过 K 步变更从当前状态变更为目标状态, 且每一步变更都符合系统的约束条件.
Input
第一行为当前配置的条目数量 N.
接下来 N 行, 每一行为一条接口配置, 每行以接口类型和接口名称开头, 针对不同接口具体格式如下:
物理接口: 接口类型 + 接口名称, 用单个空格分隔, 例如 "Physical eth1".
链路聚合接口: 接口类型 + 接口名称 + 子接口数量 + 子接口名称, 用单个空格分隔, 例如 "Bond bond1 3 eth1 eth2 eth3". 有多个子接口时, 会按接口名称的字典序排序.
VLAN 接口: 接口类型 + 接口名称 + 父接口名称, 用单个空格分隔, 例如 "VLAN vlan1 bond1".
接下来一行是目标配置的条目数量 M.
接下来 M 行, 每一行为一条接口配置, 格式和当前配置的格式一致.
当前配置和目标配置都一定是符合系统约束的.
Output
输出最小的变更步骤数量K
样例1:
输入:
5
Physical eth1
Physical eth2
Physical eth3
Bond bond1 1 eth1
VLAN vlan1 eth2
5
Physical eth1
Physical eth2
Physical eth3
Bond bond1 2 eth1 eth3
VLAN vlan1 eth2
输出:
2
程序:
// WA
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;
struct Interface {
string type;
string name;
vector<string> details; // 子接口或父接口
bool operator==(const Interface &other) const {
return type == other.type && name == other.name && details == other.details;
}
};
struct HashInterface {
size_t operator()(const Interface &iface) const {
size_t h1 = hash<string>()(iface.type);
size_t h2 = hash<string>()(iface.name);
size_t h3 = 0;
for (const string &detail : iface.details) {
h3 ^= hash<string>()(detail);
}
return h1 ^ h2 ^ h3;
}
};
// 读取接口配置
vector<Interface> readInterfaces(int count) {
vector<Interface> interfaces;
for (int i = 0; i < count; ++i) {
string type, name;
cin >> type >> name;
Interface iface{type, name, {}};
if (type == "Bond") {
int subCount;
cin >> subCount;
iface.details.resize(subCount);
for (int j = 0; j < subCount; ++j) {
cin >> iface.details[j];
}
} else if (type == "VLAN") {
string parent;
cin >> parent;
iface.details.push_back(parent);
}
interfaces.push_back(iface);
}
return interfaces;
}
// 计算最小变更步骤数
int calculateMinSteps(const vector<Interface> ¤t, const vector<Interface> &target) {
unordered_set<Interface, HashInterface> currentSet(current.begin(), current.end());
unordered_set<Interface, HashInterface> targetSet(target.begin(), target.end());
int removeCount = 0, addCount = 0;
// 找出需要移除的接口
for (const Interface &iface : current) {
if (targetSet.find(iface) == targetSet.end()) {
++removeCount;
}
}
// 找出需要添加的接口
for (const Interface &iface : target) {
if (currentSet.find(iface) == currentSet.end()) {
++addCount;
}
}
return removeCount + addCount;
}
int main() {
int n, m;
cin >> n;
vector<Interface> current = readInterfaces(n);
cin >> m;
vector<Interface> target = readInterfaces(m);
cout << calculateMinSteps(current, target) << endl;
return 0;
}
❌内存管理系统模拟
Description
在操作系统中, 内存管理是核心功能之一, 内存管理策略对于系统性能至关重要. 内存页是操作系统对物理内存管理的基本单位. 你需要设计并实现一个内存管理系统模拟器, 该系统应通过命令行支持以下功能:
- 内存分配: 模拟内存分配请求, 寻找合适的有空闲空间的内存页为其分配, 支持首次适应, 最佳适应和最差适应算法.
- 内存回收: 模拟内存回收过程, 处理内存碎片问题.
- 内存碎片分析: 计算并报告内存碎片率. (已分配使用内存页中未实际使用的内存空间为内存碎片, 内存碎片率为: 内存碎片总大小/所有内存总大小)
Input
以命令行形式输入要执行的内存相关命令.
第一行为: MEM_PAGE [大小] [个数]
- 定义内存页的大小, 和个数, 总内存即大小*个数,大小范围为[1, 20971512], 个数范围为[1, 20971512].
第二行起为用户通过命令行输入命令和参数:
- ALLOCATE_FIRST_FIT [大小]: 使用首次适应算法分配指定大小的内存块(大小不会超过一个内存页), 并返回系统分配的内存自增索引编号. 首次适应算法从内存的开始位置顺序搜索, 直到找到足够大的空闲块.
- ALLOCATE_BEST_FIT [大小]: 使用最佳适应算法分配指定大小的内存块(大小不会超过一个内存页), 并返回系统分配的内存自增索引编号. 最佳适应算法搜索整个内存, 找到能够满足要求且最小的空闲块.如果有多个同时符合条件的内存页,则分配最靠近内存开始位置的内存页.
- ALLOCATE_WORST_FIT [大小]: 使用最差适应算法分配指定大小的内存块(大小不会超过一个内存页), 并返回系统分配的内存自增索引编号. 最差适应算法搜索整个内存, 找到最大的空闲块, 即使它比所需大小大得多. 如果有多个同时符合条件的内存页,则分配最靠近内存开始位置的内存页.
- FREE [索引]: 释放指定索引处的内存块, 使其变为空闲状态.
- ANALYZE: 分析当前内存状态, 包括总内存, 已用内存, 空闲内存和内存碎片率(百分比保留两个小数点).
Output
系统根据用户命令执行相应操作, 并给出操作结果.
对于 ALLOCATE 命令, 输出一行操作结果, 如果内存分配成功, 输出 Memory Index is N, N 代表内存自增索引编号, 否则输出 Failed
对于 FREE 命令, 输出 Done
对于ANALYZE 命令, 输出四行结果, 分别代表总内存, 已用内存, 空闲内存和内存碎片率, 例如:
Total Memory: 40960
Used Memory: 250
Free Memory: 40710
Memory Fragmentation: 19.39%
样例:
Sample Input 1
MEM_PAGE 4096 10
ALLOCATE_FIRST_FIT 100
ALLOCATE_BEST_FIT 50
ALLOCATE_WORST_FIT 200
ALLOCATE_FIRST_FIT 4097
ANALYZE
FREE 1
ANALYZE
Sample Output 1
Memory Index is 1
Memory Index is 2
Memory Index is 3
Failed
Total Memory: 40960
Used Memory: 350
Free Memory: 40610
Memory Fragmentation: 19.15%
Done
Total Memory: 40960
Used Memory: 250
Free Memory: 40710
Memory Fragmentation: 19.39%
程序
❌工作流
Description
一个工作流由一组(至少一个)有序的任务组成. 一个任务拥有一个唯一的名称. 任务可能含有若干个有序的子任务 (子任务也可能含有其自己的子任务).
工作流的执行规则:
- 当一个任务执行时, 先执行其自身, 如果其包含子任务, 再按序执行其子任务.
- 默认情况下按序执行工作流中的所有任务.
- 可以指定只执行部分任务. 当某个任务被指定执行时, 其所有父任务也被视为指定执行.
- 可以指定跳过部分任务. 当某个任务被指定跳过时, 其所有子任务也被视为指定跳过.
- 当同时指定了执行的任务和跳过的任务时, 先应用规则 3, 再应用规则 4.
Input
第一行为工作流中的任务及其顺序, 格式为使用单个空格分隔的任务名称. 如“A B C”,表示工作流由“A”, “B”, “C” 3 个任务组成.
第二行为大于等于 0 的整数 N.
接下来的 N 行, 每一行表示一个任务所包含的子任务及其顺序, 格式为使用单个空格分隔的任务名称, 其中第一个任务为父任务, 其余任务为该任务的子任务.
如“A B C”,表示任务 “A” 包含 “B”、“C” 两个子任务。
接下来的一行, 表示指定执行的任务名称, 格式为使用单个空格分隔的任务名称. 如“A B C”,表示只执行“A”、“B”、“C” 3 个任务, 该行可能为空行, 该行为空代表默认执行工作流中除了指定跳过外的所有任务.
最后一行, 表示指定跳过的任务名称, 格式为使用单个空格分隔的任务名称. 如“A B C”, 表示跳过“A”、“B”、“C” 3 个任务, 该行可能为空行.
Output
输出最终实际执行的任务及其顺序,格式为使用单个空格分隔的任务名称。如“A B C”,表示最终实际按此顺序执行了“A”、“B”、“C” 3 个任务。
样例:
Sample Input 1
A B C
2
A D
D E
B E
Sample Output 1
A D E B
程序:
⭕吃个桃桃
Description
小陶玩了《黑神话 悟空》, 晚上做梦梦见自己在游戏内的世界遇到了一片神秘的森林, 森林里有N棵树, 每棵树上都有一些桃子, 并且还在不断的长出新的桃子.
天命人这时突然出现, 问小陶: “我要是随意划一片范围,你可能告诉我这一片当前总共有多少个桃子?”, 说罢随手摘了几个桃.
小陶即刻回道: “这有何难!”
Input
第一行输入为一个 int64 N, 表示接下来有 N 棵树, 之后 N 行每行一个 int64 整数 M, 第 i 行整数表示第 i 棵树当前的桃子数量. 第 N+2 行输入为一个 int64 J, 表示接下来有 J 个操作, 接下来 J 行每行三个 int64 整数 A B C, A 表示操作类型, B C 为对应操作的参数, A 共有三种可能输入:
- 0: 询问当前 [B, C] 区间上的桃子总数.
- 1: 第 B 棵树上长出了 C 个桃子.
- 2: 天命人吃了 B 棵树上 C 个桃子.
Output
输出为每行一个 int64 数字, 输出询问总数的结果.
数据规模:1 <= N, J <= 10^5
样例:
Sample Input 1
5
1
2
3
4
5
5
0 0 4
1 0 1
0 0 1
2 0 1
0 0 4
Sample Output 1
15
4
15
程序:
#include <iostream>
#include <vector>
using namespace std;
class SegmentTree {
private:
vector<int64_t> tree;
int n;
// 构建线段树
void build(const vector<int64_t>& arr, int node, int start, int end) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(arr, 2*node, start, mid);
build(arr, 2*node+1, mid+1, end);
tree[node] = tree[2*node] + tree[2*node+1];
}
}
// 更新操作
void update(int node, int start, int end, int idx, int64_t value) {
if (start == end) {
tree[node] += value;
} else {
int mid = (start + end) / 2;
if (start <= idx && idx <= mid) {
update(2*node, start, mid, idx, value);
} else {
update(2*node+1, mid+1, end, idx, value);
}
tree[node] = tree[2*node] + tree[2*node+1];
}
}
// 查询操作
int64_t query(int node, int start, int end, int l, int r) {
if (r < start || end < l) {
return 0; // 完全不重叠
}
if (l <= start && end <= r) {
return tree[node]; // 完全包含
}
// 部分重叠
int mid = (start + end) / 2;
int64_t left_query = query(2*node, start, mid, l, r);
int64_t right_query = query(2*node+1, mid+1, end, l, r);
return left_query + right_query;
}
public:
SegmentTree(const vector<int64_t>& arr) {
n = arr.size();
tree.resize(4 * n);
build(arr, 1, 0, n - 1);
}
// 更新某个位置的值
void update(int idx, int64_t value) {
update(1, 0, n - 1, idx, value);
}
// 查询区间的和
int64_t query(int l, int r) {
return query(1, 0, n - 1, l, r);
}
};
int main() {
int64_t N;
cin >> N;
vector<int64_t> trees(N);
for (int64_t i = 0; i < N; ++i) {
cin >> trees[i];
}
SegmentTree segTree(trees);
int64_t J;
cin >> J;
for (int64_t i = 0; i < J; ++i) {
int64_t A, B, C;
cin >> A >> B >> C;
if (A == 0) {
// 查询操作
cout << segTree.query(B, C) << endl;
} else if (A == 1) {
// 增加桃子操作
segTree.update(B, C);
} else if (A == 2) {
// 减少桃子操作
segTree.update(B, -C);
}
}
return 0;
}
❌指令流水线
Description
在这道题目中, 你需要模拟一个简化的指令流水线系统, 该指令流水线采用"按序发射, 按序完成”的方式, 并且没有采用转发技术处理相关数据.
流水线的指令类型:
-
LOAD R1, X
:从内存中加载数据到寄存器 R1。 -
ADD [寄存器编号], [寄存器编号|数字], [寄存器编号|数字]
ADD R2, 1, 11
:将 1 和 11 相加,并将结果存储到 R2 中ADD R2, R1, 11
:将寄存器 R1 和 11 相加,并将结果存储到 R2 中ADD R1, R2, R3
:将寄存器 R2 和 R3 的值相加,并将结果存储到 R1 中
-
SUB [寄存器编号], [寄存器编号|数字], [寄存器编号|数字]
-
STORE R1, X
:将寄存器 R1 的值存储到内存位置 X
流水线的指令执行:
周期 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
指令① | IF | ID | EX | MEM | WB | |
指令② | IF | ID | EX | MEM | WB |
- 指令流水线有五个阶段:
IF
(取指令),ID
(指令译码),EX
(执行),MEM
(内存访问),WB
(写回),每个阶段执行都需要一个时钟周期, 每一个指令都需要依次经历这 5 个阶段, 才能完成执行过程, 即一条指令至少需要经过 5 个时钟周期. - 指令流水线是为了加快一批指令的执行速度而引入的. 举例来说, 在指令之间不存在依赖关系的情况下, 当一条指令完成某个阶段后, 对应这个阶段的处理器会立即投入下一条指令该阶段的处理, 如图所示, 在 6 个周期内完成了 2 条指令的执行.
流水线的执行约束
- 资源冲突: 不存在资源冲突, 即指令执行的五个阶段各自在不同的存储设备上完成.
- 数据相关:如果一条指令依赖于前面指令的计算结果(例如使用相同的寄存器), 则必须等待前一条指令执行完毕才能继续(但这个过程不会导致 IF 冲突, 只会在 ID 及之后的环节占用对应的资源);如果一条指令存储结果的寄存器在正在被某条执行中的指令作为操作数寄存器使用,则也必须等待该指令完成才能继续,该过程同样只会在 ID 及之后的环节占用对应的资源
- 控制相关: 由于不存在控制指令, 暂不考虑.
提示
- 在整个流程中,LOAD 和 STORE 指令是特殊的
- LOAD:如果要将数据存储到某个寄存器中,这个寄存器不能已经被占用,否则可能会影响上一个指令的执行结果;当一个寄存器被 LOAD 指令占用时,这个寄存器不能在后续指令引用,否则也可能会影响后续指令的执行结果
- STROE:如果一个寄存器被 STROE 占用,这个寄存器不能在后续指令作为目标寄存器引用,否则也可能会影响后续指令的执行结果;如 store r1, x; add r1, r2, r3; 指令序列,此时后一个指令尝试改变 r1 的内容,因此不能加速执行
示例分析:
周期/阶段 | IF | ID | EX | MEM | WB |
---|---|---|---|---|---|
1 | ① | ||||
2 | ② | ① | |||
3 | ② | ① | |||
4 | ② | ① | |||
5 | ② | ① | |||
6 | ③ | ② | |||
7 | ③ | ② | |||
8 | ③ | ② | |||
9 | ③ | ② | |||
10 | ③ | ||||
11 | ③ | ||||
12 | ③ | ||||
13 | ③ |
Input
第一行是一个数字 N, 代表后续有 N 条指令.
后续 2 ~ N+1 行输入是一个指令集,指令集中的指令如前文定义,指令集按顺序给出,每条指令占一行.
Output
输出是执行完所有指令所需的最小时钟周期数。
样例:
Sample Input 1
3
LOAD R1, X
ADD R2, R1, R3
SUB R4, R2, R5
Sample Output 1
13
程序:让gpt继续优化
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <sstream>
#include <stdexcept> // For throwing exceptions
using namespace std;
enum InstructionType { LOAD, ADD, SUB, STORE };
// Helper to parse a register (R1, R2, ...) and return its index
int getRegisterIndex(const string& reg) {
// We assume the format is always like R1, R2, ..., which are valid register names.
// Extract the register number by subtracting '0' from the character at position 1.
return reg[1] - '0'; // Convert register to index (R1 -> 1)
}
// Parse a single instruction and return its type and associated registers/operands
InstructionType parseInstruction(const string& instruction, vector<int>& args) {
stringstream ss(instruction);
string op;
ss >> op;
if (op == "LOAD") {
string reg, addr;
ss >> reg >> addr;
args.push_back(getRegisterIndex(reg)); // Register
return LOAD;
} else if (op == "ADD") {
string reg1, reg2orVal, reg3orVal;
ss >> reg1 >> reg2orVal >> reg3orVal;
args.push_back(getRegisterIndex(reg1)); // Destination register
if (reg2orVal[0] == 'R') {
args.push_back(getRegisterIndex(reg2orVal)); // Register
args.push_back(getRegisterIndex(reg3orVal)); // Register
} else {
args.push_back(stoi(reg2orVal)); // Immediate value
args.push_back(stoi(reg3orVal)); // Immediate value
}
return ADD;
} else if (op == "SUB") {
string reg1, reg2orVal, reg3orVal;
ss >> reg1 >> reg2orVal >> reg3orVal;
args.push_back(getRegisterIndex(reg1)); // Destination register
if (reg2orVal[0] == 'R') {
args.push_back(getRegisterIndex(reg2orVal)); // Register
args.push_back(getRegisterIndex(reg3orVal)); // Register
} else {
args.push_back(stoi(reg2orVal)); // Immediate value
args.push_back(stoi(reg3orVal)); // Immediate value
}
return SUB;
} else if (op == "STORE") {
string reg, addr;
ss >> reg >> addr;
args.push_back(getRegisterIndex(reg)); // Register
return STORE;
} else {
// Invalid instruction case
cerr << "Error: Invalid instruction " << instruction << endl;
return static_cast<InstructionType>(-1); // Return a sentinel value to indicate an error
}
}
int main() {
int N;
cin >> N;
vector<string> instructions(N);
vector<vector<int>> args(N);
// Input instructions
for (int i = 0; i < N; ++i) {
cin.ignore();
getline(cin, instructions[i]);
}
// To track the completion time of each register and instruction stages
vector<int> regCompletion(32, -1); // Track when each register is available
vector<int> instCompletion(N, -1); // Track when each instruction is complete
int currentCycle = 0;
// Simulate the pipeline
for (int i = 0; i < N; ++i) {
vector<int> argsForCurrent;
InstructionType type = parseInstruction(instructions[i], argsForCurrent);
// If the instruction is invalid, skip to the next one
if (type == static_cast<InstructionType>(-1)) {
continue; // Skip invalid instructions
}
// Debug output for instruction and args
cout << "Processing instruction: " << instructions[i] << endl;
for (int i : argsForCurrent) {
cout << "Arg: " << i << endl;
}
// Handle the stages of the pipeline
int ifCycle = currentCycle + 1; // Instruction fetch happens next cycle
int idCycle = max(ifCycle, regCompletion[argsForCurrent[0]] + 1); // Wait for the register to be free
if (type == ADD || type == SUB) {
int reg2 = argsForCurrent[1], reg3 = argsForCurrent[2];
idCycle = max(idCycle, regCompletion[reg2] + 1); // Wait for registers
idCycle = max(idCycle, regCompletion[reg3] + 1); // Wait for registers
}
int exCycle = idCycle + 1;
int memCycle = exCycle + 1;
int wbCycle = memCycle + 1;
// Update the completion cycle for the instruction and its affected registers
instCompletion[i] = wbCycle;
regCompletion[argsForCurrent[0]] = wbCycle; // Destination register is updated at WB
// Update the current cycle tracker
currentCycle = max(currentCycle, wbCycle);
}
// Output the total number of clock cycles required
cout << "Total cycles: " << currentCycle << endl;
return 0;
}
❌最优分配问题
Description
背景:
一家公司拥有 M 名员工(M 是一个正整数),每位员工有各自的技能等级(一个正整数),表示为一个整数数组 skills,但技能越高费用越高。
公司计划启动 N 个项目(N <= M),每个项目需要指定一名负责人,且每个项目的难度等级(一个正整数)已知,表示为一个整数数组 project_difficulties。编写一个程序,输入员工技能数组 skills 和项目难度数组 project_difficulties,输出一个有效的项目负责人分配方案,使得公司在这些项目上的投入费用最低。
要求:
要求1:每位员工可以负责多个项目
要求2:每个项目只能有一名负责人。
要求3:员工负责项目的难度不能超过其自身的技能等级。
要求4:公司的目标是总技能等级之和最优。
Input
第一行是 M 个数字, 用空格分隔, 代表每个员工的技能等级.
第二行是 N 个数字, 用空格分隔, 代表每个项目的难度等级.
Output
一个数字, 代表所有项目负责人的总技能等级之和.
样例:
Sample Input 1
3 5 7 6
2 2 6
Sample Output 1
11
程序:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 输入员工的技能数组
int M;
cin >> M;
vector<int> skills(M);
for (int i = 0; i < M; ++i) {
cin >> skills[i];
}
// 输入项目的难度数组
int N;
cin >> N;
vector<int> project_difficulties(N);
for (int i = 0; i < N; ++i) {
cin >> project_difficulties[i];
}
// 排序技能数组和项目难度数组
sort(skills.begin(), skills.end());
sort(project_difficulties.begin(), project_difficulties.end());
int total_skill = 0;
int j = 0; // 指向员工技能的指针
// 为每个项目分配负责人
for (int i = 0; i < N; ++i) {
// 寻找第一个技能等级足够的员工
while (j < M && skills[j] < project_difficulties[i]) {
++j; // 跳过技能不足的员工
}
// 如果找到了合适的员工
if (j < M) {
total_skill += skills[j];
++j; // 该员工已经被分配到一个项目,指针后移
}
}
cout << total_skill << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?