用根号分块维护每块的未匹配删除和存活插入序列,以分块栈快速拼接双端队列并计算矩阵乘积。
OJ: shumeng
题目 ID: CSP202312D
难度:提高+/省选-
标签:根号分块数据结构矩阵乘法双端队列
日期: 2026-07-31 16:21
形式化题目
有
思路
先看直接按区间模拟双端队列的暴力程序,它用链表记录每个插入元素的位置,删除时删掉最近插入的那个:
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:40
*/
// brute.cpp:小数据暴力解,直接用链表模拟区间内的双端队列。
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 998244353;
struct Matrix {
long long a[2][2];
};
struct Operation {
int type;
Matrix matrix;
};
Matrix identity_matrix() {
Matrix result = {{{1, 0}, {0, 1}}};
return result;
}
Matrix multiply(const Matrix &left, const Matrix &right) {
Matrix result = {};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
result.a[i][j] = (result.a[i][j] + left.a[i][k] * right.a[k][j]) % MOD;
}
}
}
return result;
}
Operation read_operation() {
Operation result;
cin >> result.type;
if (result.type == 1 || result.type == 2) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) cin >> result.matrix.a[i][j];
}
}
return result;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, event_count;
cin >> n >> event_count;
vector<Operation> operation(n);
for (int i = 0; i < n; i++) operation[i] = read_operation();
while (event_count--) {
int event_type;
cin >> event_type;
if (event_type == 1) {
int index;
cin >> index;
operation[index - 1] = read_operation();
continue;
}
int left, right;
cin >> left >> right;
list<Matrix> queue;
vector<list<Matrix>::iterator> inserted;
for (int i = left - 1; i < right; i++) {
if (operation[i].type == 1) {
queue.push_front(operation[i].matrix);
inserted.push_back(queue.begin());
} else if (operation[i].type == 2) {
queue.push_back(operation[i].matrix);
list<Matrix>::iterator position = queue.end();
--position;
inserted.push_back(position);
} else if (!inserted.empty()) {
queue.erase(inserted.back());
inserted.pop_back();
}
}
Matrix answer = identity_matrix();
for (list<Matrix>::iterator it = queue.begin(); it != queue.end(); ++it) {
answer = multiply(answer, *it);
}
cout << answer.a[0][0] << ' ' << answer.a[0][1] << ' '
<< answer.a[1][0] << ' ' << answer.a[1][1] << '\n';
}
return 0;
}一段指令的栈式摘要
删除操作总是删除最晚插入的矩阵,因此把每次插入看成压入“插入栈”,删除就是弹出栈顶。对一个固定分块从空栈开始扫描:
- 删除时若块内栈不空,就抵消块内最后一次插入;
- 否则记为一个未匹配删除,表示它将来会删除块外传入的栈顶;
- 扫描结束后,栈中剩余的插入按时间顺序组成存活插入序列。
所以分块可以摘要为 pop_count 和一段存活插入序列。把分块作用到已有栈时,先弹出 pop_count 个元素,再把存活插入序列整体压入。
分块栈与矩阵聚合
设一段插入序列中,所有头插入矩阵按最终顺序的乘积为 front,所有尾插入矩阵按最终顺序的乘积为 back。先后相接的两段序列
每个分块预处理存活插入序列每个前缀的 (front, back)。查询时,区间两端不足一个分块的部分逐条执行,中间完整分块作为整体压入分块栈;删除操作如果只截断当前分块,就用对应前缀聚合量重算累计值。最终答案就是 front × back。
动态修改与查询
单点修改只影响所在分块,重新扫描该分块即可。一个区间至多包含两个边界分块和
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:40
*/
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 998244353;
const int BLOCK_SIZE = 320;
// 2x2 矩阵
struct Matrix {
long long a[2][2];
};
// 一条指令:type 1 头插,2 尾插,3 删除最晚插入的矩阵
struct Operation {
int type;
Matrix matrix;
};
// 一段插入序列聚合:front 为所有头插入按最终顺序的乘积,back 为所有尾插入的乘积
struct Aggregate {
Matrix front;
Matrix back;
};
// 一个分块的摘要:未匹配的删除个数 + 存活插入序列每个前缀的聚合
struct BlockSummary {
int pop_count;
vector<Aggregate> prefix;
};
// 分块栈中的一个元素:source 为分块编号(>=0)或单条指令(-index-1),length 为存活插入个数
struct ChunkState {
int source;
int length;
Aggregate all; // 从栈底到当前元素的累计聚合
};
int n, event_count, block_count;
vector<Operation> operation;
vector<BlockSummary> block;
vector<ChunkState> chunk_stack;
Matrix identity_matrix() {
Matrix result = {{{1, 0}, {0, 1}}};
return result;
}
// 矩阵乘法,模 MOD
Matrix multiply(const Matrix &left, const Matrix &right) {
Matrix result;
result.a[0][0] = (left.a[0][0] * right.a[0][0]
+ left.a[0][1] * right.a[1][0]) % MOD;
result.a[0][1] = (left.a[0][0] * right.a[0][1]
+ left.a[0][1] * right.a[1][1]) % MOD;
result.a[1][0] = (left.a[1][0] * right.a[0][0]
+ left.a[1][1] * right.a[1][0]) % MOD;
result.a[1][1] = (left.a[1][0] * right.a[0][1]
+ left.a[1][1] * right.a[1][1]) % MOD;
return result;
}
Aggregate identity_aggregate() {
Matrix identity = identity_matrix();
return {identity, identity};
}
// 拼接两段插入序列:先出现 left 再出现 right。
// 头插入的最终顺序与出现顺序相反,尾插入的顺序相同。
Aggregate concatenate(const Aggregate &left, const Aggregate &right) {
return {multiply(right.front, left.front), multiply(left.back, right.back)};
}
// 单条插入指令自身作为一个聚合
Aggregate operation_aggregate(int index) {
Aggregate result = identity_aggregate();
if (operation[index].type == 1) result.front = operation[index].matrix;
if (operation[index].type == 2) result.back = operation[index].matrix;
return result;
}
// 取一段长度为 length 的插入序列的聚合。
// source >= 0 表示分块编号,否则表示单条指令 -source-1。
Aggregate chunk_aggregate(int source, int length) {
if (source >= 0) return block[source].prefix[length];
if (length == 0) return identity_aggregate();
return operation_aggregate(-source - 1);
}
// 重新扫描一个分块,生成其摘要:先抵消块内的删除,再对存活插入做前缀聚合
void rebuild_block(int id) {
int left = id * BLOCK_SIZE;
int right = min(n, left + BLOCK_SIZE);
vector<int> push_operation; // 尚未被块内删除抵消的插入指令
block[id].pop_count = 0;
for (int i = left; i < right; i++) {
if (operation[i].type == 3) {
if (push_operation.empty()) block[id].pop_count++; // 未匹配删除
else push_operation.pop_back(); // 抵消最近一次插入
} else {
push_operation.push_back(i);
}
}
// 对存活插入序列做前缀聚合
block[id].prefix.clear();
block[id].prefix.resize(push_operation.size() + 1, identity_aggregate());
for (int i = 0; i < (int)push_operation.size(); i++) {
block[id].prefix[i + 1] = block[id].prefix[i];
int index = push_operation[i];
if (operation[index].type == 1) {
block[id].prefix[i + 1].front = multiply(
operation[index].matrix, block[id].prefix[i].front);
} else {
block[id].prefix[i + 1].back = multiply(
block[id].prefix[i].back, operation[index].matrix);
}
}
}
// 从分块栈顶弹出 count 个插入元素(模拟连续删除)
void pop_elements(int count) {
while (count > 0 && !chunk_stack.empty()) {
ChunkState &last = chunk_stack.back();
if (last.length <= count) {
count -= last.length;
chunk_stack.pop_back();
} else {
// 只截断栈顶分块:用截断后的前缀聚合重算累计值
last.length -= count;
Aggregate before = chunk_stack.size() == 1
? identity_aggregate() : chunk_stack[chunk_stack.size() - 2].all;
last.all = concatenate(before, chunk_aggregate(last.source, last.length));
count = 0;
}
}
}
// 把一个完整分块压入栈,length 为它存活插入序列的长度
void append_chunk(int source, int length) {
if (length == 0) return;
Aggregate before = chunk_stack.empty() ? identity_aggregate() : chunk_stack.back().all;
Aggregate current = chunk_aggregate(source, length);
chunk_stack.push_back({source, length, concatenate(before, current)});
}
// 把单条指令作用到分块栈上
void apply_operation(int index) {
if (operation[index].type == 3) {
pop_elements(1);
} else {
Aggregate before = chunk_stack.empty() ? identity_aggregate() : chunk_stack.back().all;
Aggregate current = before;
if (operation[index].type == 1) {
current.front = multiply(operation[index].matrix, before.front);
} else {
current.back = multiply(before.back, operation[index].matrix);
}
chunk_stack.push_back({-index - 1, 1, current});
}
}
// 把整个分块作为摘要作用到栈上:先处理未匹配删除,再压入存活插入序列
void apply_block(int id) {
pop_elements(block[id].pop_count);
append_chunk(id, (int)block[id].prefix.size() - 1);
}
// 对区间 [left, right] 执行所有指令,返回最终队列矩阵乘积
Matrix query(int left, int right) {
chunk_stack.clear();
int index = left;
while (index <= right) {
int id = index / BLOCK_SIZE;
int end = min(right, (id + 1) * BLOCK_SIZE - 1);
// 完整块用摘要整体处理,边界块逐条执行
if (index == id * BLOCK_SIZE && end - index + 1 == BLOCK_SIZE) {
apply_block(id);
} else {
for (int i = index; i <= end; i++) apply_operation(i);
}
index = end + 1;
}
if (chunk_stack.empty()) return identity_matrix();
return multiply(chunk_stack.back().all.front, chunk_stack.back().all.back);
}
// 读入一条指令
Operation read_operation() {
Operation result;
cin >> result.type;
if (result.type == 1 || result.type == 2) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) cin >> result.matrix.a[i][j];
}
}
return result;
}
void print_matrix(const Matrix &matrix) {
cout << matrix.a[0][0] << ' ' << matrix.a[0][1] << ' '
<< matrix.a[1][0] << ' ' << matrix.a[1][1] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> event_count;
operation.resize(n);
for (int i = 0; i < n; i++) operation[i] = read_operation();
// 预处理每个分块的摘要
block_count = (n + BLOCK_SIZE - 1) / BLOCK_SIZE;
block.resize(block_count);
for (int i = 0; i < block_count; i++) rebuild_block(i);
while (event_count--) {
int type;
cin >> type;
if (type == 1) {
// 单点修改:更新指令并重建所在分块
int index;
cin >> index;
operation[index - 1] = read_operation();
rebuild_block((index - 1) / BLOCK_SIZE);
} else {
int left, right;
cin >> left >> right;
print_matrix(query(left - 1, right - 1));
}
}
return 0;
}复杂度
设分块长
总结
删除“最晚插入”使指令具有栈结构,根号分块可以把每块压缩成有限的未匹配删除和存活插入序列。分别维护头插入和尾插入的矩阵乘积,就能在动态修改下快速回答区间密码。