我的代码:
#include <iostream>//用于输入输出流的操作
#include <vector>//用于动态数组的操作
#include <map>//包含了 C++ 标准库中的 map 容器
/*map 是一种关联容器,用于存储键值对(key-value pairs),
并且每个键只能出现一次,键值对会根据键的大小自动排序*/
#include <stack>//是 C++ 标准库中的 stack 容器的头文件
/*stack 是一种后进先出(LIFO, Last In First Out)数据结构,
它允许在容器的末端进行元素的插入和删除操作*/
using namespace std;
int main() {
int n;//n 表示矩阵的数量
while (cin >> n) {
//定义了一个整型变量 n,并且通过 cin 从输入流读取数据
//while(cin >> n) 使得程序会一直运行直到没有更多输入
vector<vector<int>> m(n, vector<int>(2, 0));
/*用来存储每个矩阵的维度信息
这里使用的是二维 vector*/
for (int i = 0; i < n; ++i) {//读取每个矩阵的维度
cin >> m[i][0] >> m[i][1];
//m[i][0] 存储矩阵的行数,m[i][1] 存储矩阵的列数
}
stack<vector<int>> st;
/*用于存储矩阵的维度
栈中的每一项是一个包含矩阵行列数的 vector<int>,
用于模拟矩阵的操作*/
int ans = 0;//用于累加计算矩阵乘法的总运算次数
for (int i = 0; i < 3 * n - 2; ++i) {
//字符串的长度为 n + 2*(n-1)
char ch;//用来存储每个字符
cin >> ch;
//从输入流读取一个字符(这个字符可能是 (, ), 或者字母)
if (ch == '(') {
continue;
//如果字符是左括号 '(',则跳过不做处理
//(左括号不影响栈的操作)
} else if (ch == ')') {
//如果字符是右括号 ')',就需要进行矩阵乘法的计算
vector<int> a(2, 0);
vector<int> b(2, 0);
/*定义了两个 vector<int>,a 和 b,
它们都初始化为含有 2 个元素且值为 0 的向量
每个向量的大小是 2,表示存储一个矩阵的维度信息:
a[0] 和 a[1] 存储矩阵 A 的维度(A 的行数和列数)
b[0] 和 b[1] 存储矩阵 B 的维度(B 的行数和列数)*/
b = st.top();
/*st.top() 获取栈顶元素
(即栈中最新压入的矩阵维度)
将栈顶的矩阵维度赋值给 b,表示矩阵 B 的维度*/
st.pop();//通过 st.pop() 将该元素从栈中弹出
a = st.top();
//*将栈顶的矩阵维度赋值给 a,表示矩阵 A 的维度
st.pop();//将矩阵 A 的维度从栈中移除
ans += a[0] * a[1] * b[1];
//用于计算两个矩阵相乘时的乘法运算次数
/*假设矩阵 A 的维度是 a[0] x a[1],
矩阵 B 的维度是 b[0] x b[1]
两个矩阵相乘时,计算的运算次数是
A 的行数(a[0])乘以 A 的列数(a[1])和 B 的列数(b[1])
即,矩阵乘法的基本运算量为 a[0] * a[1] * b[1]
将这个运算量加到 ans 上,表示当前计算中所需的乘法次数*/
//将计算后的新矩阵大小压入栈内
st.emplace(vector<int> {a[0], b[1]});
/*创建了一个新的 vector<int> 对象,
并用 {a[0], b[1]} 来初始化它
emplace 是 STL 容器 stack 提供的一种方法,
它可以直接在栈中构造一个对象,
而不需要先创建该对象并再将其压入栈中
emplace 相比于 push 更高效,
因为它避免了创建临时对象的步骤*/
} else {
st.emplace(m[ch - 'A']);//字母,使对应的矩阵大小入栈
/*这里的 ch - 'A' 会将字母 ch 转换成其对应的索引
(例如,如果 ch 是 'A',那么 ch - 'A' 就是 0,'B' 是 1,依此类推)
m[ch - 'A'] 返回 m 数组中第 ch - 'A' 行的矩阵维度
(即一个包含两个整数的 vector,代表矩阵的行数和列数)*/
}
}
cout << ans << endl;
}
return 0;
}
思路总结
这个程序的目标是计算给定一系列矩阵乘法所需的最小运算次数;程序通过模拟矩阵链乘法问题(Matrix Chain Multiplication),利用栈来跟踪矩阵的维度,并根据括号内的操作顺序计算运算量
主要步骤:
-
输入矩阵维度:
- 输入一个整数
n
,表示矩阵的数量 - 然后为每个矩阵输入两个整数,表示矩阵的行数和列数,存储在二维
vector<int>
中
- 输入一个整数
-
使用栈跟踪矩阵维度:
使用一个stack
来存储矩阵的维度信息;每次遇到左括号(
时,栈中的矩阵维度不会发生变化;遇到右括号)
时,才会进行矩阵乘法的计算 -
处理矩阵乘法:
- 矩阵乘法的规则是:给定两个矩阵 A (a1 × a2) 和 B (b1 × b2),它们能够相乘当且仅当 A 的列数等于 B 的行数(即 a2 = b1);矩阵乘法的运算量是
a1 * a2 * b2
- 每次遇到
)
,程序会从栈中弹出两个矩阵的维度,计算乘法的运算量,然后将新得到的矩阵(结果矩阵)的维度压回栈中
- 矩阵乘法的规则是:给定两个矩阵 A (a1 × a2) 和 B (b1 × b2),它们能够相乘当且仅当 A 的列数等于 B 的行数(即 a2 = b1);矩阵乘法的运算量是
-
计算总运算量:
计算过程中,累加每一次矩阵乘法所需的运算次数,并最终输出总的运算量
核心实现:
- 栈的操作:通过
stack
容器模拟矩阵乘法操作的顺序,push
和pop
操作分别处理矩阵维度的入栈和出栈 - 运算量计算:每进行一次矩阵乘法时,通过计算
a[0] * a[1] * b[1]
得到运算次数,并加到总的运算量ans
中
代码中的关键操作:
-
矩阵维度的存储:
vector<vector<int>> m(n, vector<int>(2, 0));
用于存储每个矩阵的行和列数m[ch - 'A']
将字符转化为对应的矩阵维度
-
栈的应用:
stack<vector<int>> st;
用于存储矩阵维度- 使用
emplace
方法将矩阵的维度直接压入栈中,相比push
更加高效
-
括号解析:
- 左括号
(
不做任何操作,继续等待矩阵维度的输入 - 右括号
)
时,进行矩阵乘法的计算,并将计算后的新矩阵压入栈
- 左括号
总结:
这个程序通过栈的结构模拟了矩阵乘法的过程,利用矩阵链乘法的原理计算了最小的乘法运算次数;其实现思路清晰,充分利用了 C++ STL 容器 stack
和 vector
来有效地解决问题
emplace
是 C++ 中 STL 容器(如 std::vector
、std::set
、std::map
等)提供的一个非常有用的成员函数;它的作用是直接在容器中构造元素,而不是先创建一个临时对象再进行复制或移动插入;这样能够提高效率,尤其是对于包含复杂类型的容器
emplace
函数的特点
- 原地构造:
emplace
直接在容器的内部空间构造元素,而不需要先构造一个临时对象再进行复制或移动 - 避免不必要的拷贝/移动:相比
insert
等方法,emplace
不会创建额外的临时对象,因此可以避免不必要的拷贝或移动操作
基本语法
container.emplace(args...);
args...
是构造元素所需的参数,它们会直接传递给元素类型的构造函数
例子:std::vector
#include <iostream>
#include <vector>
int main() {
std::vector<std::pair<int, int>> vec;
// 使用 emplace 添加元素
vec.emplace_back(1, 2); // 直接在容器内部构造一个 pair<int, int> 对象
// 输出
for (const auto& p : vec) {
std::cout << p.first << ", " << p.second << std::endl;
}
return 0;
}
在这个例子中,emplace_back(1, 2)
直接在 vec
容器中构造了一个 std::pair<int, int>
,而不是先创建一个临时 pair
对象再将其添加到容器中
例子:std::set
#include <iostream>
#include <set>
int main() {
std::set<int> st;
// 使用 emplace 插入元素
st.emplace(10); // 直接在容器中构造值为 10 的元素
// 输出
for (const auto& e : st) {
std::cout << e << std::endl;
}
return 0;
}
在 std::set
中,emplace
用来插入元素,set
会根据给定的值直接在内部构造元素;由于 set
中的元素不允许重复,emplace
会自动处理元素的唯一性
与 insert
的对比
-
insert
需要先创建一个对象,再将其插入到容器中:st.insert(10); // 先创建一个临时元素,再插入
-
emplace
直接在容器中构造元素,无需临时对象:st.emplace(10); // 直接在容器中构造元素
何时使用 emplace
?
- 当容器中存储的类型比较复杂,包含多个成员,或者元素的构造比较昂贵时,
emplace
会更加高效 - 当你需要避免不必要的拷贝或移动时,
emplace
是一个很好的选择 - 如果你只需要传递参数构造元素,并且不需要先创建一个临时对象,可以选择使用
emplace
总结
emplace
是一个高效的插入方式,它避免了不必要的拷贝或移动操作,通过直接在容器内部构造元素,能够显著提高性能;特别是在存储复杂类型元素的容器中,emplace
能提供更大的优势
栈(Stack)是一种数据结构,它遵循后进先出(LIFO, Last In First Out)的原则;也就是说,最后压入栈的元素会最先被弹出
栈的主要操作
栈有两个主要的操作:
- 压栈(Push):将一个元素添加到栈顶
- 弹栈(Pop):移除栈顶的元素,并返回该元素
此外,栈通常还会提供以下辅助操作:
- 栈顶(Top/Peek):返回栈顶的元素,但不移除它
- 判断栈是否为空(IsEmpty):检查栈中是否还有元素
- 栈的大小(Size):返回栈中元素的个数
栈的应用场景
栈通常用于处理具有递归结构的问题,或需要按照顺序执行某些操作的情境,常见的应用包括:
-
表达式求值:
在计算机科学中,栈常用于处理算术表达式的求值,尤其是在中缀表达式转后缀表达式的过程中 -
函数调用栈:
在程序的执行过程中,栈被用来跟踪函数的调用和返回;当一个函数被调用时,程序会将该函数的状态(如局部变量、返回地址等)压入栈中;函数执行完毕后,状态从栈中弹出,返回给调用者 -
回溯算法:
在解决问题时,栈可以用来保存当前的状态,并在需要时回到上一步进行尝试;这种方法常用于路径搜索、迷宫求解等 -
括号匹配:
栈在括号匹配算法中应用广泛,比如验证括号是否成对出现,或计算括号表达式的结果
C++ 中的栈
C++ 标准库提供了 stack
容器,位于 <stack>
头文件中;它的基本操作包括:
- push():将元素压入栈中
- pop():将栈顶元素弹出
- top():返回栈顶元素
- empty():检查栈是否为空
- size():返回栈中的元素个数
下面是一个 C++ 栈的简单示例:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
// 向栈中压入元素
s.push(10);
s.push(20);
s.push(30);
// 打印栈顶元素
cout << "栈顶元素: " << s.top() << endl; // 输出 30
// 弹出栈顶元素
s.pop();
// 打印栈顶元素
cout << "弹出栈顶元素后,栈顶元素: " << s.top() << endl; // 输出 20
// 判断栈是否为空
if (s.empty()) {
cout << "栈为空" << endl;
} else {
cout << "栈不为空,栈大小为: " << s.size() << endl; // 输出 栈大小为 2
}
return 0;
}
栈的特点
- 后进先出(LIFO):栈的操作遵循 LIFO 原则,保证了最新压入栈的元素最先被处理
- 空间效率:栈通常只需要操作栈顶元素,操作简单,且空间开销较小
- 限制性:栈只能从栈顶访问元素,无法像队列一样从两端访问,因此灵活性较低
总结来说,栈是一种非常实用的数据结构,广泛应用于需要跟踪顺序或递归操作的问题中