CCF-CSP认证考试 202312-4 宝藏 题解
一、题目介绍(CSDN截图)
二、思路及方法
- 难点:1.直接模拟队列操作会超时,m,n可达
2.队列操作是双端的,而且有删除操作
3.支持单点修改
- 思路:将每一条指令看作一个操作,可以对队列产生影响;
用线段树来维护区间上的指令复合效果;
将队列建模成两个栈,左栈(头部插入的矩阵) 右栈(尾部插入的矩阵),删除操作 会从两个栈中pop最近的那个
三、代码展示(此代码还不完全正确,对m,n<=1000适用)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;const int MOD = 998244353;struct Matrix {long long a, b, c, d;Matrix() : a(1), b(0), c(0), d(1) {}Matrix(long long a_, long long b_, long long c_, long long d_) : a(a_ % MOD), b(b_ % MOD), c(c_ % MOD), d(d_ % MOD) {}
};Matrix mul(const Matrix& A, const Matrix& B) {return Matrix((A.a * B.a + A.b * B.c) % MOD,(A.a * B.b + A.b * B.d) % MOD,(A.c * B.a + A.d * B.c) % MOD,(A.c * B.b + A.d * B.d) % MOD);
}Matrix IDENTITY() {return Matrix(1, 0, 0, 1);
}struct Op {int type; // 1: L, 2: R, 3: DMatrix mat;
};struct Node {vector<Matrix> left_mats; // 头部插入的矩阵(新到旧)vector<Matrix> right_mats; // 尾部插入的矩阵(旧到新)int delete_count;Node() : delete_count(0) {}
};vector<Op> instr;
vector<Node> seg;
int n, m;// 计算队列的乘积
Matrix calc_product(const Node& node) {vector<Matrix> queue;// 头部插入的矩阵要逆序(因为插入顺序是新到旧,但队列顺序是旧到新)for (int i = node.left_mats.size() - 1; i >= 0; i--) {queue.push_back(node.left_mats[i]);}// 尾部插入的顺序就是队列顺序for (const auto& mat : node.right_mats) {queue.push_back(mat);}if (queue.empty()) return IDENTITY();Matrix res = queue[0];for (int i = 1; i < queue.size(); i++) {res = mul(res, queue[i]);}return res;
}Node merge(const Node& left, const Node& right) {Node res;// 复制左边的矩阵vector<Matrix> left_stack = left.left_mats;vector<Matrix> right_stack = left.right_mats;// 执行右边的操作// 1. 右边的头部插入for (const auto& mat : right.left_mats) {left_stack.push_back(mat);}// 2. 右边的尾部插入 for (const auto& mat : right.right_mats) {right_stack.push_back(mat);}// 3. 处理删除操作int del_count = left.delete_count + right.delete_count;// 删除操作:优先删除左边栈(头部),然后右边栈(尾部)while (del_count > 0 && (!left_stack.empty() || !right_stack.empty())) {if (!left_stack.empty()) {left_stack.pop_back();} else {right_stack.pop_back();}del_count--;}res.left_mats = left_stack;res.right_mats = right_stack;res.delete_count = 0; // 删除操作已经处理完毕return res;
}void build(int idx, int l, int r) {if (l == r) {Node& node = seg[idx];if (instr[l].type == 1) {node.left_mats.push_back(instr[l].mat);} else if (instr[l].type == 2) {node.right_mats.push_back(instr[l].mat);} else {node.delete_count = 1;}return;}int mid = (l + r) / 2;build(idx * 2, l, mid);build(idx * 2 + 1, mid + 1, r);seg[idx] = merge(seg[idx * 2], seg[idx * 2 + 1]);
}void update(int idx, int l, int r, int pos) {if (l == r) {Node& node = seg[idx];node.left_mats.clear();node.right_mats.clear();node.delete_count = 0;if (instr[l].type == 1) {node.left_mats.push_back(instr[l].mat);} else if (instr[l].type == 2) {node.right_mats.push_back(instr[l].mat);} else {node.delete_count = 1;}return;}int mid = (l + r) / 2;if (pos <= mid) update(idx * 2, l, mid, pos);else update(idx * 2 + 1, mid + 1, r, pos);seg[idx] = merge(seg[idx * 2], seg[idx * 2 + 1]);
}Node query(int idx, int l, int r, int ql, int qr) {if (ql <= l && r <= qr) return seg[idx];int mid = (l + r) / 2;if (qr <= mid) return query(idx * 2, l, mid, ql, qr);if (ql > mid) return query(idx * 2 + 1, mid + 1, r, ql, qr);Node left = query(idx * 2, l, mid, ql, qr);Node right = query(idx * 2 + 1, mid + 1, r, ql, qr);return merge(left, right);
}int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;instr.resize(n);for (int i = 0; i < n; i++) {int t;cin >> t;if (t == 1) {long long a, b, c, d;cin >> a >> b >> c >> d;instr[i] = {1, Matrix(a, b, c, d)};} else if (t == 2) {long long a, b, c, d;cin >> a >> b >> c >> d;instr[i] = {2, Matrix(a, b, c, d)};} else {instr[i] = {3, IDENTITY()};}}seg.resize(4 * n);build(1, 0, n - 1);while (m--) {int type;cin >> type;if (type == 1) {int i;cin >> i;i--;int t;cin >> t;if (t == 1) {long long a, b, c, d;cin >> a >> b >> c >> d;instr[i] = {1, Matrix(a, b, c, d)};} else if (t == 2) {long long a, b, c, d;cin >> a >> b >> c >> d;instr[i] = {2, Matrix(a, b, c, d)};} else {instr[i] = {3, IDENTITY()};}update(1, 0, n - 1, i);} else {int l, r;cin >> l >> r;l--; r--;Node res = query(1, 0, n - 1, l, r);Matrix ans = calc_product(res);cout << ans.a << " " << ans.b << " " << ans.c << " " << ans.d << "\n";}}return 0;
}
四、代码详解
1.线段树节点设计
struct Node {
vector<Matrix> left_mats; // 模拟头部插入的矩阵(新到旧)
vector<Matrix> right_mats; // 模拟尾部插入的矩阵(旧到新)
int delete_count; //删除操作的数量
Node() : delete_count(0) {}
};
头部插入:新矩阵放在最前面
尾部插入:新矩阵放在最后面
删除最近插入:删除最近插入的矩阵
2.叶子节点构建
if (instr[l].type == 1) {
//头部插入:加入到left_mats中
node.left_mats.push_back(instr[l].mat);
} else if (instr[l].type == 2) {
//尾部插入:加入到right_mats中
node.right_mats.push_back(instr[l].mat);
} else {
//记录删除数量
node.delete_count = 1;
}
3.合并操作(核心)
Node merge(const Node& left, const Node& right) {
Node res;
// 复制左节点的所有矩阵
vector<Matrix> left_stack = left.left_mats;
vector<Matrix> right_stack = left.right_mats;
// 执行右边的操作
// 1. 右边的头部插入
for (const auto& mat : right.left_mats) {
left_stack.push_back(mat);
}
// 2. 右边的尾部插入
for (const auto& mat : right.right_mats) {
right_stack.push_back(mat);
}
// 3. 处理删除操作
int del_count = left.delete_count + right.delete_count;
// 删除操作:优先删除左边栈(头部),然后右边栈(尾部)
while (del_count > 0 && (!left_stack.empty() || !right_stack.empty())) {
if (!left_stack.empty()) {
left_stack.pop_back(); //删除最近的头部
} else {
right_stack.pop_back(); //删除最近的尾部
}
del_count--;
}
res.left_mats = left_stack;
res.right_mats = right_stack;
res.delete_count = 0; // 删除操作已经处理完毕
return res;
}
4..对最后队列中所有矩阵的乘积算法
队列为空,则输出单位矩阵;队列为1,则输出该矩阵;队列长度大于1,则计算乘积
Matrix calc_product(const Node& node) {
vector<Matrix> queue;
// 头部插入的矩阵要逆序(因为插入顺序是新到旧,但队列顺序是旧到新)
for (int i = node.left_mats.size() - 1; i >= 0; i--) {
queue.push_back(node.left_mats[i]);
}
// 尾部插入的顺序就是队列顺序
for (const auto& mat : node.right_mats) {
queue.push_back(mat);
}
if (queue.empty()) return IDENTITY();
Matrix res = queue[0];
for (int i = 1; i < queue.size(); i++) {
res = mul(res, queue[i]);
}
return res;
}
5.建树
void build(int idx, int l, int r) {
if (l == r) {
//叶子节点:处理单条指令
Node& node = seg[idx];
if (instr[l].type == 1) {
node.left_mats.push_back(instr[l].mat);
} else if (instr[l].type == 2) {
node.right_mats.push_back(instr[l].mat);
} else {
node.delete_count = 1;
}
return;
}
int mid = (l + r) / 2;
build(idx * 2, l, mid); //递归建左子树
build(idx * 2 + 1, mid + 1, r); //递归建右子树
seg[idx] = merge(seg[idx * 2], seg[idx * 2 + 1]); //合并左右子树的结果
}
6.更新
void update(int idx, int l, int r, int pos) {
if (l == r) {
//找到目标叶子节点,重新设置
Node& node = seg[idx];
node.left_mats.clear();
node.right_mats.clear();
node.delete_count = 0;
if (instr[l].type == 1) {
node.left_mats.push_back(instr[l].mat);
} else if (instr[l].type == 2) {
node.right_mats.push_back(instr[l].mat);
} else {
node.delete_count = 1;
}
return;
}
int mid = (l + r) / 2;
if (pos <= mid) update(idx * 2, l, mid, pos); //在左子树
else update(idx * 2 + 1, mid + 1, r, pos); //在右子树
seg[idx] = merge(seg[idx * 2], seg[idx * 2 + 1]); //更新当前节点(重新合并子节点)
}
7.查询
Node query(int idx, int l, int r, int ql, int qr) {
//当前区间完全包含在查询区间内
if (ql <= l && r <= qr) return seg[idx];
int mid = (l + r) / 2;
if (qr <= mid) return query(idx * 2, l, mid, ql, qr); //完全在左子树
if (ql > mid) return query(idx * 2 + 1, mid + 1, r, ql, qr); //完全在右子树
//跨区间查询
Node left = query(idx * 2, l, mid, ql, qr);
Node right = query(idx * 2 + 1, mid + 1, r, ql, qr);
return merge(left, right);
}
五、总结
线段树是一种二叉树,用于高效处理区间查询和区间更新问题。
想象你要管理一个数组,经常需要:
· 查询某个区间的和/最大值/最小值
· 修改某个位置的值
如果每次查询都遍历区间,时间复杂度是O(n)。线段树通过预处理和树形结构,把这些操作降到O(log n)。