P6109 [Ynoi2009] rprmq1
题面
有一个 \(n \times n\) 的矩阵 \(a\),初始全是 \(0\)。
先进行 \(m\) 次修改操作,一次修改操作会给出 \(l_1,l_2,r_1,r_2,x\),代表把所有满足 \(l_1 \le i \le r_1\) 且 \(l_2 \le j \le r_2\) 的 \(a_{i,j}\) 元素加上一个值 \(x\)。
在进行 \(q\) 次查询操作,一次查询操作会给出 \(l_1,l_2,r_1,r_2\),代表查询所有满足 \(l_1 \le i \le r_1\) 且 \(l_2 \le j \le r_2\) 的 \(a_{i,j}\) 元素的最大值。
数据范围:\(n,m\le 5\times 10^4,q\le 5\times 10^5\) 。
题解
先有暴力做法:
离线下来,然后扫描线,支持区间加减,此时维护的就是 \(i=x\) 的一竖列的值的情况。
但是题目要我们求的是矩形的最大值,也就是随着 \(i\) 从左到右一段时间内的区间最大值。
这看起来很难做,我们可以放宽限制,考虑如果是一段前缀可以做吗?
诶,这不就是线段树区间历史最值吗?
现在回到原情况,现在相当于我们可以从某个点开始,求一段前缀、后缀的的历史最大值。
想到了什么?
分治!
我们把询问分治到 \(l\le mid\le r\) 的一个节点上,然后对每个节点从 \(mid\) 扫到 \(l\) 记录答案,然后撤回,在从 \(mid+1\) 扫到 \(r\) 并记录答案。
但是!我们发现并不是直接扫就能得到答案的,因为对于时刻 \(i\) 一竖列的值的情况是操作的前缀和!所以我们必须安排一个合适的扫描+回退的顺序。
考虑处理 \(l,r\) ,并且当指针位于 \(l-1\) (指针的位置表示处理到的位置) ,我们希望处理完之后退回到 \(l-i\) ,操作如下:
- 指针扫到 \(mid\) 。
- 对历史最值初始化。
- 扫到 \(r\) ,更新答案。
- 回退到 \(mid\) 。
- 递归处理 \(mid+1,r\) ,处理完指针还在 \(mid\) 。
- 对历史最值初始化。
- 扫到 \(l-1\) ,更新答案。
注意对历史最值初始化怎么做?
-
方法一:
可以在线段树上额外开一个标记表示是否要初始化,初始化后会让 \(hma=ma,hbj=-\inf\) 。
注意在给 \(p\) 打标记的时候要先
down(p)
,否则就会出现 \(p\) 的标记没有先下传!代码上是这样的:
void chuli2(int p){ chuli1(p<<1,tr[p].bj1,tr[p].bj2),chuli1(p<<1|1,tr[p].bj1,tr[p].bj2); tr[p].bj=1,tr[p].hma=tr[p].ma,tr[p].bj2=inf,tr[p].bj1=0; }
-
方法二:
可以给线段树整体加一个比较大的数(防止爆
long long
),这样历史就更新了。
启发
- 分治的新方式!
- 历史最值在处理二维最值上的作用!