1 条题解
-
0
题目理解
我们有一个 的矩阵 ,对于每个区间 (),定义:
$$M(x, y) = \max_{i=1}^k \left( \min_{j=x}^y a_{i,j} \right) $$函数 ,其中 是按位异或。
对于每个查询 ,需要计算:
问题分析
核心难点
- 直接枚举所有 是 的,不可行
- 是二维的 RMQ 问题:先每行做区间最小值,然后所有行取最大值
关键观察
当固定右端点 ,左端点 从 递减到 时, 是单调不增的,并且只会变化 次。
这是因为对于每行, 关于 是单调不增的,而 是 个单调不增函数的 ,所以它的值域变化次数是 。
代码解法详解
1. 预处理 ST 表
void make_st() { for (int i = 2 ; i <= n ; i ++) Lg2[i] = Lg2[i >> 1] + 1 ; for (int k = 1 ; k <= K ; k ++) { for (int i = 0 ; i <= 16 ; i ++) { if (i == 0) { for (int j = 1 ; j <= n ; j ++) st[k][i][j] = a[k][j] ; } else { for (int j = 1 ; j <= n - (1 << i) + 1 ; j ++) { st[k][i][j] = min(st[k][i - 1][j], st[k][i - 1][j + (1 << (i - 1))]) ; } } } } }为每行建立 RMQ 的 ST 表,用于 查询任意区间的行最小值。
2. 单调栈思想 + 二分查找变化点
for (int i = 1 ; i <= n ; i ++) { int j = i ; for (int tim = 0 ; tim <= 3 * K && j ; tim ++) { int v = ask(j, i) ; // 计算 M(j, i) // 二分找到最大的左端点 pos,使得 M(pos, i) = v // 那么区间 [ans, j] 的 M(·, i) 都等于 v // 用线段树更新这个区间 change(1, ans, j) ; j = ans - 1 ; } }对于每个右端点 ,从 开始向左找所有 的变化点。由于最多只有 个变化点,复杂度为 每个右端点。
3. 线段树维护区间和
线段树节点存储三个值:
f[0]:区间内所有 的和f[1]:区间长度f[2]:常数 1(用于矩阵乘法)
使用矩阵乘法来批量更新区间:
tmp.mat[0][0] = 1, tmp.mat[0][1] = 0, tmp.mat[0][2] = 0 ; tmp.mat[1][0] = 1, tmp.mat[1][1] = 0, tmp.mat[1][2] = 0 ; tmp.mat[2][0] = 0, tmp.mat[2][1] = ((v * B + C)^A), tmp.mat[2][2] = 1 ;这个矩阵的作用是:
f[0]保持不变(第一行)f[1]保持不变(第二行)f[2]转换为F(v)加到f[1]上(第三行)
4. 查询处理
将所有查询按右端点排序,当处理到右端点 时,回答所有右端点为 的查询:
for (int id : que[i]) { ans[id] = query(1, l[id], n) ; }
算法流程
- 预处理:为每行建立 ST 表
- 枚举右端点:从 到
- 使用二分找到所有 的变化点
- 用线段树批量更新每个值相同的区间
- 回答查询:当处理到查询的右端点时,查询线段树得到答案
复杂度分析
- 预处理:
- 主循环:(每个右端点 )
- 查询:
- 总复杂度:,对于 可接受
算法标签
- 数据结构
- 线段树
- ST 表 (RMQ)
- 矩阵乘法优化
- 单调栈思想
- 离线查询
- 预处理
总结
这道题的解法非常巧妙:
- 利用单调性:发现 关于 单调且变化点很少
- 离线处理:按右端点排序查询,逐步构建答案
- 矩阵批量更新:用矩阵乘法高效更新线段树区间
- 二分找变化点:快速定位值变化的边界
核心思想是将 的区间枚举优化到 ,通过充分利用问题的单调性和离线处理技巧,实现了高效求解。
- 1
信息
- ID
- 4003
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者